# Automate It: CI/CD Build → Push → Deploy

In the [first tutorial](https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy) 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](https://docs.danubedata.ro/cli-overview) for how to create one and
  authenticate; the CLI reads it from the `DANUBE_TOKEN` environment variable.
- The `hello` Rapid 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`:

```yaml
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/amd64`** matters 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:

```bash
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`:

```yaml
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`:

```yaml
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:

```bash
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](https://docs.danubedata.ro/tutorial-cr-rapids-troubleshooting)
if a deploy gets stuck.

## What's next

- **[Production-Ready: Domains, Secrets & Autoscaling](https://docs.danubedata.ro/tutorial-cr-rapids-production)** —
  put a real domain in front and tune how it scales.
- **[Git Deployments](https://docs.danubedata.ro/serverless-git)** — 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
