Remote

CI

Deploy to a machine from a CI pipeline.

Ant is a single binary, so a CI job is three steps: install it, sign in with a ci-role account, and deploy. Nothing prompts, and every failure exits non-zero.

One-time setup

  1. Invite the pipeline's account with the ci role. ci can deploy, roll back, and read status and logs; it cannot change the roster, read the audit log, or hold OS accounts.
    ant colony invite --role ci --machine prod
    # or, once the account's NodeID is known:
    ant colony users add <nodeid> --machine prod --role ci
    

    The pipeline needs its own account identity: sign up once, redeem the invite, then export it.
    ant colony signup --name ci
    ant colony join --token "$(cat invite.txt)" --alias prod
    ant colony export --out ant-ci.json
    
  2. Store the bundle and passphrase as CI secrets: the bundle file as ANT_BUNDLE and its passphrase as ANT_PASSPHRASE (masked).
  3. Register the machine so the runner can dial it. The NodeID comes from ant nest bootstrap's output or ant nest identity show on the machine:
    ant nest machines add prod --hostname prod.example.com --node-id <nodeid>
    

GitHub Actions

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ANT_MACHINE: prod
      ANT_PASSPHRASE: ${{ secrets.ANT_PASSPHRASE }}
    steps:
      - uses: actions/checkout@v6
      - name: Install ant
        run: |
          curl -fsSL https://ants-docs.pages.dev/dl/install.sh | sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: Sign in
        run: |
          printf '%s' "${{ secrets.ANT_BUNDLE }}" > /tmp/ant-ci.json
          ant colony login --bundle /tmp/ant-ci.json
      - name: Register the machine
        run: ant nest machines add prod --node-id "${{ vars.ANT_MACHINE_NODE_ID }}"
      - name: Deploy
        run: ant trail deploy --release "$GITHUB_SHA" --json | tee deploy.json

GitLab CI

deploy:
  stage: deploy
  image: docker:27
  services: [docker:27-dind]
  variables:
    ANT_MACHINE: prod
  script:
    - apk add --no-cache curl
    - curl -fsSL https://ants-docs.pages.dev/dl/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"
    - printf '%s' "$ANT_BUNDLE" > /tmp/ant-ci.json
    - ant colony login --bundle /tmp/ant-ci.json
    - ant nest machines add prod --node-id "$ANT_MACHINE_NODE_ID"
    - ant trail deploy --release "$CI_COMMIT_SHA" --json
  only: [main]

ANT_PASSPHRASE, ANT_BUNDLE, and ANT_MACHINE_NODE_ID are masked CI variables. The install script and the release downloads need no credentials. A runner with Docker is only needed for source builds; a project with deploy.build: none (or the --image form) builds nothing locally.

Split build and deploy

Build once in a fast job, push to the registry, and deploy in another job that needs neither the source nor a local build:

# job A, needs deploy.registry in ant.yaml and ANT_REGISTRY_PASSWORD
ant haul --push --release "$GITHUB_SHA"

# job B, runs the pushed image on the machine; set ANT_REGISTRY_USERNAME and
# ANT_REGISTRY_PASSWORD here too, and the pull credential travels with the deploy
ant trail deploy --image ghcr.io/org/app:"$GITHUB_SHA" --pull --machine prod

For a private image, job B either sets ANT_REGISTRY_USERNAME + ANT_REGISTRY_PASSWORD (the credential rides the deploy to the machine and is gone when it finishes) or the machine carries its own docker login.

ant haul --push always builds on the runner (the image must exist there to be pushed) and supports container projects; compose projects push their service images with docker.

Reading the result

--json prints the deploy or rollback result on stdout while build output stays on stderr, so a pipeline can consume it directly:

release=$(ant trail deploy --json | jq -r .release)
ant trail rollback --to "$release" --json

Fields: app, deployment, machine, runner, builder, image, release, container, compose, routes, warnings.

Rollback from CI

ant trail rollback --to "$PREVIOUS_RELEASE" --json

rollback.keep_releases (or deploy.keep_images) controls how many releases stay on the machine; the target image tag must still exist there.

Notes

  • The runner needs a Linux amd64/arm64 build with CGO; the release binaries are. The macOS/Windows CLI builds run local-first and cannot reach a machine.
  • ANT_MACHINE is the environment fallback for --machine, so a pipeline sets the target once.
  • The bundle is the account key. Scope it with the ci role per machine, keep it in a secret store, and rotate by removing the member and issuing a new invite.
  • Worker and CLI must speak the same protocol version; upgrade the machine with ant nest bootstrap when the CLI reports a mismatch.
Copyright © 2026