bootload
⚓ portal

📖 DOCS

CI/CD pipelines

Wire up automated deploys so every push to your main branch builds a fresh image and rolls it onto your bootload service — with a health check that keeps the old version serving if the new one is broken.

A pipeline on bootload is three moves:

  1. Once: deploy the service and mint a scoped CI token.
  2. Every push: build an image → push it to a registry → bootload restart <service> --image <ref>.
  3. Guardrail: the roll is health-gated. If the new image never passes its health check, the old replicas keep serving — and you bootload rollback.

restart --image is the whole trick. It starts a new deployment that rolls out one replica at a time; the public route only switches to the new image once a replica is healthy. A bad build doesn't take your site down, it just fails to take over.

1. Deploy the service once

CI rolls an existing service — it never creates one. Create it once so its name, URL, and size are fixed:

bootload deploy --project my-project --name web \
  --image ghcr.io/acme/web:bootstrap --port 8080:http

--port must match what your image actually listens on (nginx → 80:http, a typical Node app → 3000:http). A public HTTPS URL is assigned automatically and printed as a route line.

2. Mint a scoped CI token

Give CI its own key — never your personal login. These are the scopes a build-and-roll pipeline needs:

bootload token create ci-web \
  --scope projects:read \
  --scope services:read --scope services:write \
  --scope deployments:read --scope deployments:write \
  --scope registry:push --scope registry:pull \
  --project <project-id> \
  --resource service:<service-id>

The secret (blt_…) is printed once — store it as a CI secret named BOOTLOAD_TOKEN. A few things worth knowing:

3. Keep a wallet balance

Deploys and rolls draw from the project's prepaid wallet. An empty wallet fails the roll with wallet balance is zero. Keep a balance, or turn on auto-top-up in the portal, before you rely on CI.

4. Choose a registry

bootload's built-in registry — self-contained, no external accounts:

bootload image login --org my-org
bootload image push web:$TAG --org my-org      # → registry.bootload.io/my-org/web:$TAG
bootload restart web --project my-project --image registry.bootload.io/my-org/web:$TAG

Your own registry (GHCR, Docker Hub, ECR, …) — build and push with its normal tooling, then point bootload at the reference. For a private image, store pull credentials once so bootload can pull it at deploy time:

bootload registry add ghcr --project my-project \
  --url ghcr.io --username <user> --token <read:packages token>

Public images need no credentials.

5. GitHub Actions

Build and push to GHCR with the built-in GITHUB_TOKEN, then roll bootload and wait for the rollout to go healthy — rolling back if it doesn't. GitHub's ubuntu-latest runner uses a plaintext Docker config, so registry logins just work.

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

concurrency:            # never let two rolls race
  group: deploy-web
  cancel-in-progress: false

jobs:
  ship:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    env:
      IMAGE: ghcr.io/${{ github.repository }}/web
      BOOTLOAD_TOKEN: ${{ secrets.BOOTLOAD_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - name: Build and push
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          TAG=${{ github.sha }}
          docker build -t "$IMAGE:$TAG" .
          docker push "$IMAGE:$TAG"
          echo "REF=$IMAGE:$TAG" >> "$GITHUB_ENV"

      - name: Install the bootload CLI
        run: curl -fsSL https://bootload.io/v1/cli/install.sh | sh

      - name: Roll and verify
        env:
          PROJECT: acme-prod
          SERVICE: web
        run: |
          bootload restart "$SERVICE" --project "$PROJECT" --image "$REF"
          for i in $(seq 1 60); do
            line=$(bootload deployments "$SERVICE" --project "$PROJECT" --plain | head -1)
            case "$line" in
              *succeeded*) echo "rolled out: $line"; exit 0 ;;
              *failed*|*error*) echo "failed: $line"
                                bootload rollback "$SERVICE" --project "$PROJECT"; exit 1 ;;
            esac
            echo "… $line"; sleep 5
          done
          bootload rollback "$SERVICE" --project "$PROJECT"; exit 1

For a private repo image, run bootload registry add ghcr … once (step 4) so bootload can pull it — the workflow itself doesn't change.

6. GitLab CI

This one uses bootload's built-in registry, so there's no second registry to manage:

# .gitlab-ci.yml
stages: [deploy]

deploy:
  stage: deploy
  image: docker:27
  services: [docker:27-dind]
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
    ORG: acme
    PROJECT: acme-prod
    SERVICE: web
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  resource_group: deploy-web
  script:
    - apk add --no-cache curl
    - curl -fsSL https://bootload.io/v1/cli/install.sh | sh
    - TAG=$CI_COMMIT_SHORT_SHA
    - docker build -t web:$TAG .
    - bootload image login --org "$ORG"
    - bootload image push web:$TAG --org "$ORG"
    - REF=registry.bootload.io/$ORG/web:$TAG
    - bootload restart "$SERVICE" --project "$PROJECT" --image "$REF"
    - |
      for i in $(seq 1 60); do
        line=$(bootload deployments "$SERVICE" --project "$PROJECT" --plain | head -1)
        case "$line" in
          *succeeded*) exit 0 ;;
          *failed*|*error*) bootload rollback "$SERVICE" --project "$PROJECT"; exit 1 ;;
        esac
        sleep 5
      done
      bootload rollback "$SERVICE" --project "$PROJECT"; exit 1

7. Verifying a rollout

bootload restart --image returns as soon as the deployment is accepted, not when it's live — so a real pipeline must wait for the result:

Skip this and a broken image silently never takes over while CI reports green.

Gotchas

For AI coding agents

Both flows above ship as an installable agent skill, so a coding agent can set up your pipeline for you: bootload.io/skills/ci-cd-on-bootload/SKILL.md (and deploy-on-bootload for a first manual deploy).