{"slug":"tutorial-cr-rapids-cicd","title":"Automate It: CI/CD Build → Push → Deploy","description":"In the first tutorial you built","section":"Features","url":"https://docs.danubedata.ro/tutorial-cr-rapids-cicd","markdown_url":"https://docs.danubedata.ro/tutorial-cr-rapids-cicd.md","breadcrumbs":[{"title":"Features","slug":null},{"title":"Rapids","slug":"serverless-overview"},{"title":"Deploy with CR: CI/CD","slug":"tutorial-cr-rapids-cicd"}],"headings":[{"level":1,"title":"Automate It: CI/CD Build → Push → Deploy","id":"automate-it-cicd-build-push-deploy"},{"level":2,"title":"The pipeline","id":"the-pipeline"},{"level":2,"title":"What you need","id":"what-you-need"},{"level":2,"title":"Step 1: Store your secrets","id":"step-1-store-your-secrets"},{"level":2,"title":"Step 2: The GitHub Actions workflow","id":"step-2-the-github-actions-workflow"},{"level":3,"title":"Why not just push :latest?","id":"why-not-just-push-latest"},{"level":2,"title":"GitLab CI","id":"gitlab-ci"},{"level":2,"title":"Bitbucket Pipelines","id":"bitbucket-pipelines"},{"level":2,"title":"Rolling back","id":"rolling-back"},{"level":2,"title":"What's next","id":"whats-next"}],"format":"markdown","word_count":951,"content":"# Automate It: CI/CD Build → Push → Deploy\n\nIn the [first tutorial](https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy) you built\nan image, pushed it to `cr.danubedata.ro`, and deployed it by hand. Now we'll wire that loop\ninto CI so **every push to `main`** builds a fresh image, pushes it to your registry under an\nimmutable tag, and rolls a new zero-downtime Rapids revision — no dashboard clicks.\n\nThis page is language-agnostic: it operates on \"the image\" you built earlier. The same\npipeline works whether your app is Node, Go, Python, or anything else with a `Dockerfile`.\n\n## The pipeline\n\n```\ngit push  →  build image  →  push to cr.danubedata.ro  →  danube rapids update  →  new revision live\n```\n\nThe key idea is **immutable tags**: CI tags each build with the commit SHA\n(`cr.danubedata.ro/acme/hello:<sha>`) and points the Rapid at that exact tag. Kubernetes will\nnot re-pull a `:latest` tag it has already cached, so SHA tags are what make \"deploy on every\npush\" actually deploy.\n\n## What you need\n\n- A Git repo containing your app and `Dockerfile` (from the first tutorial), already pushed\n  to GitHub, GitLab, or Bitbucket.\n- A **Container Registry access key** with **Push + Pull** scope (Container Registry →\n  Access Keys). This is the `cr_...` token CI uses to push.\n- A **DanubeData API token** for the CLI to roll the deploy. See the\n  [CLI Overview](https://docs.danubedata.ro/cli-overview) for how to create one and\n  authenticate; the CLI reads it from the `DANUBE_TOKEN` environment variable.\n- The `hello` Rapid you created in tutorial 1 (CI updates it; it does not create a new one).\n\n## Step 1: Store your secrets\n\nAdd these in your CI provider's secret store (GitHub: **Settings → Secrets and variables →\nActions**):\n\n| Name | Value | Secret? |\n|------|-------|---------|\n| `DD_REGISTRY_USERNAME` | your account email (e.g. `you@example.com`) | no — can be a plain variable |\n| `DD_REGISTRY_TOKEN` | the `cr_...` registry token | **yes** |\n| `DANUBE_TOKEN` | your DanubeData API token | **yes** |\n\nOnly the tokens need to be secret; the username is informational (the registry identifies the\nkey by hashing the token, not by the username).\n\n## Step 2: The GitHub Actions workflow\n\nCreate `.github/workflows/deploy.yml`:\n\n```yaml\nname: build-push-deploy\n\non:\n  push:\n    branches: [main]\n\njobs:\n  ship:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: docker/setup-buildx-action@v3\n\n      - name: Log in to DanubeData Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: cr.danubedata.ro\n          username: ${{ secrets.DD_REGISTRY_USERNAME }}\n          password: ${{ secrets.DD_REGISTRY_TOKEN }}\n\n      - name: Build and push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          platforms: linux/amd64\n          push: true\n          tags: |\n            cr.danubedata.ro/acme/hello:${{ github.sha }}\n            cr.danubedata.ro/acme/hello:latest\n\n      - name: Roll a new Rapids revision\n        env:\n          DANUBE_TOKEN: ${{ secrets.DANUBE_TOKEN }}\n        run: |\n          npm install -g @danubedata/cli\n          danube rapids update hello \\\n            --image cr.danubedata.ro/acme/hello \\\n            --tag ${{ github.sha }}\n```\n\nThat's the whole loop. `danube rapids update ... --tag <sha>` points the Rapid at the new\nimmutable tag, which triggers a fresh Knative revision; traffic shifts to it once it passes\nits readiness check, with zero downtime. The old revision keeps serving until then.\n\n> **`platforms: linux/amd64`** matters even in CI if you ever build locally on Apple Silicon —\n> pin it so the architecture is always what the cluster runs.\n\n### Why not just push `:latest`?\n\nIf your Rapid is pinned to `:latest`, pushing a new `:latest` won't redeploy on its own —\nthe running pod keeps the image it already pulled. You'd have to force a re-roll:\n\n```bash\ndanube rapids redeploy hello   # re-pulls the current tag into a new revision\n```\n\nImmutable SHA tags avoid this entirely and give you a precise image-to-commit trail, so we\nrecommend the `update --tag <sha>` approach above. Keep pushing `:latest` too if you like it\nas a \"most recent\" pointer for humans.\n\n## GitLab CI\n\n`.gitlab-ci.yml`:\n\n```yaml\nstages: [ship]\n\nship:\n  stage: ship\n  image: docker:24\n  services:\n    - docker:24-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  script:\n    - echo \"$DD_REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u \"$DD_REGISTRY_USERNAME\" --password-stdin\n    - docker build --platform linux/amd64 -t cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA .\n    - docker push cr.danubedata.ro/acme/hello:$CI_COMMIT_SHORT_SHA\n    - apk add --no-cache nodejs npm\n    - npm install -g @danubedata/cli\n    - danube rapids update hello --image cr.danubedata.ro/acme/hello --tag $CI_COMMIT_SHORT_SHA\n  only:\n    - main\n```\n\nDefine `DD_REGISTRY_USERNAME`, `DD_REGISTRY_TOKEN`, and `DANUBE_TOKEN` under **Settings →\nCI/CD → Variables** (mark the tokens **Masked** and **Protected**).\n\n## Bitbucket Pipelines\n\n`bitbucket-pipelines.yml`:\n\n```yaml\nimage: node:20\npipelines:\n  branches:\n    main:\n      - step:\n          name: build-push-deploy\n          services: [docker]\n          script:\n            - echo \"$DD_REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u \"$DD_REGISTRY_USERNAME\" --password-stdin\n            - docker build --platform linux/amd64 -t cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT .\n            - docker push cr.danubedata.ro/acme/hello:$BITBUCKET_COMMIT\n            - npm install -g @danubedata/cli\n            - danube rapids update hello --image cr.danubedata.ro/acme/hello --tag $BITBUCKET_COMMIT\n```\n\nDefine the same three variables under **Repository settings → Repository variables** (mark\nthe tokens **Secured**).\n\n## Rolling back\n\nEvery deploy is a revision. To see history and roll back to a known-good image:\n\n```bash\ndanube rapids deployments hello     # list revisions, tags, traffic %\ndanube rapids update hello --image cr.danubedata.ro/acme/hello --tag <older-sha>\n```\n\nBecause you tagged by commit SHA, rolling back is just pointing the Rapid at an earlier tag.\nSee [Troubleshooting CR + Rapids](https://docs.danubedata.ro/tutorial-cr-rapids-troubleshooting)\nif a deploy gets stuck.\n\n## What's next\n\n- **[Production-Ready: Domains, Secrets & Autoscaling](https://docs.danubedata.ro/tutorial-cr-rapids-production)** —\n  put a real domain in front and tune how it scales.\n- **[Git Deployments](https://docs.danubedata.ro/serverless-git)** — if you'd rather have\n  Rapids build from source on each push (buildpacks/Dockerfile) instead of running your own CI.\n\n---\n\n**Questions?** Contact support at support@danubedata.ro\n","prev":{"title":"Deploy with CR: First Deploy","slug":"tutorial-cr-rapids-first-deploy","url":"https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy","markdown_url":"https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy.md","json_url":"https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy.json"},"next":{"title":"Deploy with CR: Production","slug":"tutorial-cr-rapids-production","url":"https://docs.danubedata.ro/tutorial-cr-rapids-production","markdown_url":"https://docs.danubedata.ro/tutorial-cr-rapids-production.md","json_url":"https://docs.danubedata.ro/tutorial-cr-rapids-production.json"},"index_url":"https://docs.danubedata.ro/index.json"}