Deploy from CI
When your CI builds the image, the flow is: CI builds and pushes it, then tells Kuben to roll it out. That second step is a single PATCH. It needs a Kuben token, which CI gets in one of two ways:
- a scoped API token stored as a CI secret (any CI system), below;
- a CI trust policy: GitHub Actions exchanges its own OIDC token for a short-lived Kuben token, and nothing is stored. See GitHub Actions without a stored secret.
1. Create a token
Section titled “1. Create a token”-
Open API tokens in the sidebar and fill in Create a token:
- Name:
github-actions - Role:
developer. It can deploy and change apps, but not administer. - Project (optional):
shop - Environment (optional):
staging - Expires in (days): 90 by default, 365 at most.
Then press Create token.
- Name:
-
Copy the token. It is shown once; only its SHA-256 hash is stored.
-
Store it as the repository secret
KUBEN_TOKEN, and the console URL as the variableKUBEN_URL.
Tokens are made by a signed-in person, in the console or with POST /api/v1/tokens from a session; another token cannot create one.
2. Roll out from the pipeline
Section titled “2. Roll out from the pipeline”The request that deploys a new image:
curl -fsS -X PATCH "$KUBEN_URL/api/v1/projects/shop/environments/staging/apps/api" \ -H "Authorization: Bearer $KUBEN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"image": "ghcr.io/acme/shop-api:b8e2f31"}'PATCH is a partial update: omitted fields stay as they are, and lists (env, domains, volumes) are replaced only when you send them. Every change becomes a numbered release. Use a new tag for every build, such as the commit SHA: sending the image the app already has changes nothing, even if the tag now points somewhere else.
The kuben binary does the same as a client, and waits for the rollout. With KUBEN_URL and KUBEN_TOKEN set, it needs no login:
kuben deploy shop/staging/api --image "ghcr.io/acme/shop-api:${GITHUB_SHA}"It resolves the tag to its digest, deploys on top of the revision it saw (a newer deploy is never replaced silently), and exits non-zero if the rollout fails; kuben status shop/staging/api then shows why. It resolves tags without registry credentials, so for a private image pass the digest instead: --image ghcr.io/acme/shop-api@sha256:…. --no-wait returns once the deployment is accepted, and --timeout (600 seconds by default) bounds the wait. See the client commands.
GitHub Actions
Section titled “GitHub Actions”name: deployon: push: branches: [main]permissions: contents: read packages: writejobs: deploy: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }} - name: Roll out on Kuben env: KUBEN_URL: ${{ vars.KUBEN_URL }} KUBEN_TOKEN: ${{ secrets.KUBEN_TOKEN }} IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }} run: | curl -fsS -X PATCH "$KUBEN_URL/api/v1/projects/shop/environments/staging/apps/api" \ -H "Authorization: Bearer $KUBEN_TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"image\": \"$IMAGE\"}"GitLab CI
Section titled “GitLab CI”deploy: stage: deploy image: curlimages/curl:8.10.1 script: - > curl -fsS -X PATCH "$KUBEN_URL/api/v1/projects/shop/environments/staging/apps/api" -H "Authorization: Bearer $KUBEN_TOKEN" -H 'Content-Type: application/json' -d "{\"image\": \"$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\"}" only: [main]GitHub Actions without a stored secret
Section titled “GitHub Actions without a stored secret”A CI trust policy lets a GitHub Actions workflow exchange the OIDC token GitHub gives it for a short-lived Kuben token. There is no Kuben secret in the repository to leak or rotate.
-
Turn it on in the server’s configuration:
[ci] github_actions = true(KUBEN_CI__GITHUB_ACTIONS=true), withserver.public_urlset; see Configuration. With Helm:--set ci.githubActions.enabled=true(see Helm values). -
Open Settings in the sidebar. Under CI trust policies, press Add a policy and fill in:
- Name, and the Project (and optionally one Environment) the tokens may act on;
- Role:
developerdeploys;adminalso promotes; - Repository, Repository id and Owner id: GitHub’s numeric
repository_idandrepository_owner_iddecide, so a renamed repository keeps working; - Refs (for example
refs/heads/main), GitHub environments and Workflow events, one per line (events default topush,workflow_dispatchandrelease); - Token life (seconds): 900 by default, 60 to 3600.
Only owners can manage policies, from a signed-in session. The workflow needs the policy’s id, which
GET /api/v1/ci/trust-policieslists. -
In the workflow, request an OIDC token for Kuben’s public URL and exchange it:
permissions:contents: readid-token: writesteps:- name: Get a Kuben tokenenv:KUBEN_URL: ${{ vars.KUBEN_URL }} # exactly server.public_urlPOLICY: ${{ vars.KUBEN_POLICY }} # the policy's idrun: |oidc=$(curl -fsS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$KUBEN_URL" | jq -r .value)token=$(curl -fsS -X POST "$KUBEN_URL/api/v1/ci/github/token" \-H "Authorization: Bearer $oidc" -H 'Content-Type: application/json' \-d "{\"policy\": \"$POLICY\"}" | jq -r .token)echo "::add-mask::$token"echo "KUBEN_TOKEN=$token" >> "$GITHUB_ENV"Later steps deploy with
$KUBEN_TOKENexactly as above.
The exchanged token acts with the rights of the person who made the policy, capped at the policy’s role. Each GitHub token can be exchanged once. Pull request events (pull_request, pull_request_target, workflow_run) and refs/pull/* are always refused, so code from a fork never gets a token. Revoke on a policy stops new exchanges and every token already issued under it.
What can hold a deploy
Section titled “What can hold a deploy”- A change freeze on the environment refuses new releases with
409. - Pause delivery on the app accepts the deployment, and it waits until delivery resumes.
- A production environment needs one approval per deployment by default, from someone other than the requester, so a CI deploy there waits for it. A token can never approve.
3. Promote to production
Section titled “3. Promote to production”Developers deploy to staging; admins promote to production. Promotion copies the image, commands, ports, health check and environment variables to the target environment and keeps the target’s domains, scaling and volumes. A dry_run shows the diff first. See Promote between environments.
Revoking
Section titled “Revoking”Press Revoke on the token under API tokens, or call DELETE /api/v1/tokens/{id} from a signed-in session. It stops working immediately on every replica, and the revocation is in the audit log.