Automate It: CI/CD Build → Push → Deploy
In the first tutorial you built an image, pushed it to cr.danubedata.ro, and deployed it by hand. Now we'll wire that loop into CI so every push to main builds a fresh image, pushes it to your registry under an immutable tag, and rolls a new zero-downtime Rapids revision — no dashboard clicks.
This page is language-agnostic: it operates on "the image" you built earlier. The same pipeline works whether your app is Node, Go, Python, or anything else with a Dockerfile.
The pipeline
git push → build image → push to cr.danubedata.ro → danube rapids update → new revision live
The key idea is immutable tags: CI tags each build with the commit SHA (cr.danubedata.ro/acme/hello:<sha>) and points the Rapid at that exact tag. Kubernetes will not re-pull a :latest tag it has already cached, so SHA tags are what make "deploy on every push" actually deploy.
What you need
- A Git repo containing your app and
Dockerfile(from the first tutorial), already pushed to GitHub, GitLab, or Bitbucket. - A Container Registry access key with Push + Pull scope (Container Registry → Access Keys). This is the
cr_...token CI uses to push. - A DanubeData API token for the CLI to roll the deploy. See the CLI Overview for how to create one and authenticate; the CLI reads it from the
DANUBE_TOKENenvironment variable. - The
helloRapid you created in tutorial 1 (CI updates it; it does not create a new one).
Step 1: Store your secrets
Add these in your CI provider's secret store (GitHub: Settings → Secrets and variables → Actions):
| Name | Value | Secret? |
|---|---|---|
DD_REGISTRY_USERNAME | your account email (e.g. you@example.com) | no — can be a plain variable |
DD_REGISTRY_TOKEN | the cr_... registry token | yes |
DANUBE_TOKEN | your DanubeData API token | yes |
Only the tokens need to be secret; the username is informational (the registry identifies the key by hashing the token, not by the username).
Step 2: The GitHub Actions workflow
Create .github/workflows/deploy.yml:
name: build-push-deploy
on:
push:
branches: [main]
jobs:
ship:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Log in to DanubeData Container Registry
uses: docker/login-action@v3
with:
registry: cr.danubedata.ro
username: ${{ secrets.DD_REGISTRY_USERNAME }}
password: ${{ secrets.DD_REGISTRY_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: |
cr.danubedata.ro/acme/hello:${{ github.sha }}
cr.danubedata.ro/acme/hello:latest
- name: Roll a new Rapids revision
env:
DANUBE_TOKEN: ${{ secrets.DANUBE_TOKEN }}
run: |
npm install -g @danubedata/cli
danube rapids update hello \
--image cr.danubedata.ro/acme/hello \
--tag ${{ github.sha }}
That's the whole loop. danube rapids update ... --tag <sha> points the Rapid at the new immutable tag, which triggers a fresh Knative revision; traffic shifts to it once it passes its readiness check, with zero downtime. The old revision keeps serving until then.
platforms: linux/amd64matters even in CI if you ever build locally on Apple Silicon — pin it so the architecture is always what the cluster runs.
Why not just push :latest?
If your Rapid is pinned to :latest, pushing a new :latest won't redeploy on its own — the running pod keeps the image it already pulled. You'd have to force a re-roll:
danube rapids redeploy hello # re-pulls the current tag into a new revision
Immutable SHA tags avoid this entirely and give you a precise image-to-commit trail, so we recommend the update --tag <sha> approach above. Keep pushing :latest too if you like it as a "most recent" pointer for humans.
GitLab CI
.gitlab-ci.yml:
stages: [ship]
ship:
stage: ship
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- echo "$DD_REGISTRY_TOKEN" | docker login cr.danubedata.ro -u "$DD_REGISTRY_USERNAME" --password-stdin
- docker build --platform linux/amd64 -t cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA .
- docker push cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA
- apk add --no-cache nodejs npm
- npm install -g @danubedata/cli
- danube rapids update hello --image cr.danubedata.ro/acme/hello --tag $CI_COMMIT_SHORT_SHA
only:
- main
Define DD_REGISTRY_USERNAME, DD_REGISTRY_TOKEN, and DANUBE_TOKEN under Settings → CI/CD → Variables (mark the tokens Masked and Protected).
Bitbucket Pipelines
bitbucket-pipelines.yml:
image: node:20
pipelines:
branches:
main:
- step:
name: build-push-deploy
services: [docker]
script:
- echo "$DD_REGISTRY_TOKEN" | docker login cr.danubedata.ro -u "$DD_REGISTRY_USERNAME" --password-stdin
- docker build --platform linux/amd64 -t cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT .
- docker push cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT
- npm install -g @danubedata/cli
- danube rapids update hello --image cr.danubedata.ro/acme/hello --tag $BITBUCKET_COMMIT
Define the same three variables under Repository settings → Repository variables (mark the tokens Secured).
Rolling back
Every deploy is a revision. To see history and roll back to a known-good image:
danube rapids deployments hello # list revisions, tags, traffic %
danube rapids update hello --image cr.danubedata.ro/acme/hello --tag <older-sha>
Because you tagged by commit SHA, rolling back is just pointing the Rapid at an earlier tag. See Troubleshooting CR + Rapids if a deploy gets stuck.
What's next
- Production-Ready: Domains, Secrets & Autoscaling — put a real domain in front and tune how it scales.
- Git Deployments — if you'd rather have Rapids build from source on each push (buildpacks/Dockerfile) instead of running your own CI.
Questions? Contact support at support@danubedata.ro