knext

GitHub Action

Deploy ACME from GitHub Actions with a kubeconfig — no cloud credentials, no provisioning.

Deploy on every push, from your own CI, to your own cluster.

.github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci

      - uses: docker/login-action@v3
        with:
          registry: registry.example.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_TOKEN }}

      - uses: getknext-dev/knext/packages/kn-next-action@v1
        with:
          kubeconfig: ${{ secrets.KUBECONFIG_B64 }}
          namespace: acme
          registry: registry.example.com/acme
          registry-token: ${{ secrets.REGISTRY_TOKEN }}
          skip-upload: 'true'

That is the whole thing. Push to main, and ACME redeploys.

What this action deliberately does not do

It never asks for cloud-account credentials, and it does not create clusters.

That is a design decision, not an omission. Creating managed Kubernetes requires permissions that cannot be scoped down. On AWS, eksctl's own documented minimum grants iam:CreateRole + iam:AttachRolePolicy + iam:PassRole — which together are the textbook privilege-escalation primitive, with no conditions attached to stop it. A credential that can create a cluster can generally do anything in the account, and the bill arrives either way.

A kubeconfig is different in kind. It is scoped to one cluster, and can be scoped further to one namespace. Leaked, it costs you that cluster — not your cloud account.

So: you bring a cluster, this action does everything after it.

Two shapes, and which to pick

Credential-free — skip-upload: 'true'

Needs only a kubeconfig and registry auth. Nothing cloud-specific.

You lose static-asset offload to object storage and the post-deploy asset GC. The app still builds, deploys, serves and scales to zero. Start here — it is the shortest path to a working pipeline, and you can add uploads later without changing anything else.

Full parity — assets uploaded

knext shells out to your storage provider's CLI, so authenticate it before the deploy step, using whichever action your provider already publishes:

.github/workflows/deploy.yml (GCS example)
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
          service_account: ${{ secrets.GCP_SA }}

      - uses: getknext-dev/knext/packages/kn-next-action@v1
        with:
          kubeconfig: ${{ secrets.KUBECONFIG_B64 }}
          namespace: acme
          registry: registry.example.com/acme
          registry-token: ${{ secrets.REGISTRY_TOKEN }}
          bucket: acme-assets

The equivalents are aws-actions/configure-aws-credentials, azure/login, or mc alias set for MinIO. In every case the action authenticates to a bucket you already own — it never creates one.

Prefer workload identity / OIDC over long-lived keys where your provider offers it. The knext action does not care which you use; it only needs the provider CLI to be authenticated when it runs.

Getting the kubeconfig

Do not use your personal admin kubeconfig. The action refuses one — see below — so the fastest path is also the correct one:

shell
kn-next init-ci --namespace my-app

That writes two files and touches nothing in your cluster:

  • .github/workflows/knext-deploy.yml — the workflow, with every secret it needs documented at the top
  • knext-ci-rbac.yaml — a ServiceAccount, a Role and a RoleBinding

Read the manifest, then apply it yourself:

shell
kubectl apply -f knext-ci-rbac.yaml

Build a kubeconfig from the knext-deployer ServiceAccount's token, base64-encode it, and store it as an encrypted repository secret under Settings → Secrets and variables → Actions.

shell
base64 -w0 < ci-kubeconfig.yaml    # macOS: base64 -i ci-kubeconfig.yaml

What the credential can actually do

This is the whole Role. There is no second page:

knext-ci-rbac.yaml (excerpt)
rules:
  - apiGroups:
      - apps.kn-next.dev
    resources:
      - nextapps
    verbs:
      - get
      - list
      - create
      - patch
      - update

No delete, so a leaked token cannot remove your app. No secrets, so it cannot read your database password. No Deployments, Services or Pods at all.

That is possible because deploying is writing one object. The operator running in your cluster watches for it and does everything else — Knative configuration, autoscaling, networking. CI never needs to touch any of it.

An over-broad kubeconfig is refused, not merely discouraged. On startup the action asks your cluster what the credential can do. If it can read Secrets, act on Deployments, or carries a wildcard rule, the job fails and prints the Role above instead of deploying. Asking for less than you are offered is the point — a CI secret has a wider blast radius than a laptop.

If your cluster's authorizer cannot answer that question, the action fails rather than guessing. A check that passes when it cannot see is not a check. skip-credential-preflight: true exists for that case, and turns the check off rather than satisfying it.

Inputs

InputRequiredDefaultNotes
kubeconfigyesBase64-encoded, from the scoped ServiceAccount
namespaceyesNo default: it is what bounds the credential's blast radius
registryyesRegistry host and repository, e.g. ghcr.io/acme/app
registry-tokennoOn GHCR, github.token suffices
registry-usernamenogithub.actor
authnokubeconfigReserved for short-lived cloud credentials later
dry-runnofalsePrints the NextApp resource, applies nothing
working-directoryno.Where kn-next.config.ts lives
doctornotrueRead-only preflight; fails fast on an unready cluster
skip-credential-preflightnofalseTurns off the credential check. Read the callout above first

The action sets no outputs. The deployed URL is reported by the cluster — read it with kubectl get ksvc -n <namespace>, or kn-next status. An output that was empty on every dry run and unreliable otherwise would be worse than none.

Preview deploys on pull requests

dry-run prints the NextApp custom resource that would be applied, without touching the cluster:

.github/workflows/pr.yml
on: pull_request

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - uses: getknext-dev/knext/packages/kn-next-action@v1
        with:
          kubeconfig: ${{ secrets.KUBECONFIG_B64 }}
          namespace: acme
          registry: registry.example.com/acme
          dry-run: 'true'

This makes the diff reviewable before it lands — the same instinct as terraform plan.

Why doctor runs first

By default the action runs the read-only kn-next doctor preflight and fails the job if the cluster is not ready. It checks that the CRD exists, the operator is Ready, Knative Serving is installed, and the ingress class matches the controller actually serving traffic.

These failures are much cheaper to read here than halfway through a container build. Set doctor: 'false' to skip it, though there is rarely a good reason to.

On this page