Skip to content

Set up CI

Run tide plan on pull requests and tide apply on merge, authenticating with the identity the CI runner already has, with no repository secrets.

Run tide plan on pull requests and tide apply on merge, with no repository secrets: the CI job authenticates with the identity its runner already has.

1. Add a federation rule

On the console’s Callers page, open the caller’s enrolment dialog and add a rule: the issuer (https://token.actions.githubusercontent.com for GitHub Actions), the audience (atlantis-enroll unless you change it on both sides), and a subject pattern such as repo:acme/api:*.

The enrolment listener verifies each job’s OIDC token against the issuer’s published keys under that rule and issues a one-hour certificate. The runner generates its own key inside the job; only a certificate signing request travels, and the credential dies with the runner. The certificate renews within the budget the rule sets — once, by default — and then refuses, so a credential stolen off a runner is useful for at most two hours.

2. The pull-request workflow

name: schema
on: [pull_request]

permissions:
  id-token: write     # the workload identity tide exchanges
  contents: read

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: curl -fsSL https://releases.tryatlantis.dev/install.sh | sh
      - run: tide login --oidc --url=https://enroll.example.com
      - run: tide plan
      - run: tide generate --check --against-server

--url is the organisation’s enrolment listener. tide generate --check alone needs no credentials, so it can gate a pull request before any identity exists; --against-server also fails when the server’s schema has moved since the client was generated, and rides the same one-hour certificate.

3. Rehearse before unattended applies

On the auto_verified tier, a backfill-required change applies unattended only behind a fresh passing rehearsal; auto_all extends that to destructive changes. Additive changes apply unattended on both tiers with no rehearsal, and a cross-caller breaking change always waits for a person. The merge workflow rehearses before it applies:

name: schema-apply
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: curl -fsSL https://releases.tryatlantis.dev/install.sh | sh
      - run: tide login --oidc --url=https://enroll.example.com
      - run: tide rehearse
      - run: tide apply --wait-for-approval=30m

tide rehearse executes the migration against a disposable clone of the real database. Exit 0 is a pass; exit 2 means the real apply would fail the same way — fix the schema or the data; exit 4 means the rehearsal could not answer, which the tiers treat as no. The verdict is consumed within an hour, so rehearse in the same job as the apply. Rehearsing requires the caller’s rehearsal grant (Manage members and callers).

4. Branch on the exit codes

Exit From Meaning What CI does
0 any Applied, passed, or nothing to do Pass
1 plan, apply Backfill required Re-run apply with --backfill, or fail for a human
1 generate --check Committed client is stale Fail; run tide generate and commit
2 plan Cross-caller breaking Block the merge
2 apply Waiting for approval — or the request was rejected; the message says which Treat as waiting; --wait-for-approval polls for you and stops on a rejection
2 rehearse The migration fails on real data Block the merge
3 any Operational error (parse, network, config) Fail
4 plan Destructive Block; a person decides whether losing the rows is intended
4 rehearse Unverified — the rehearsal could not answer Block, or fall back to the approval queue

Verify

Open a pull request with an additive .atl change: the plan step passes. Merge it: the apply step prints ✔ applied at <timestamp> with the new content hash, and the console’s History page shows the change attributed to the CI caller.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close