Skip to main content

CI/CD with Queen

Queen links migrations into a project-specific Go binary. Build that binary once per release, verify it against the migration sources, and deploy the same artifact that passed the tests.

GitHub Actions example for a consuming application​

Copy this workflow into the application repository as .github/workflows/migrations.yml. It expects the application to have cmd/migrate/main.go and a migrations directory as produced by queen init. Configure PRODUCTION_DATABASE_URL as a secret in the production environment. The deployment runner must be able to reach PostgreSQL.

name: Database migrations

on:
workflow_dispatch:

permissions:
contents: read

jobs:
package:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: queen_ci
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d queen_ci"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: go test ./...
- run: go build -o queen-migrate ./cmd/migrate
- run: ./queen-migrate verify-registry --dir migrations
- name: Exercise migrations and rollback on PostgreSQL
env:
QUEEN_DRIVER: postgres
QUEEN_DSN: postgres://postgres:postgres@localhost:5432/queen_ci?sslmode=disable
run: ./queen-migrate check --rollback-test --no-gaps
- uses: actions/upload-artifact@v4
with:
name: queen-migrate
path: queen-migrate
if-no-files-found: error

deploy-migrations:
needs: package
runs-on: ubuntu-latest
environment: production
concurrency:
group: queen-production-migrations
cancel-in-progress: false
env:
QUEEN_DRIVER: postgres
QUEEN_DSN: ${{ secrets.PRODUCTION_DATABASE_URL }}
steps:
- uses: actions/download-artifact@v4
with:
name: queen-migrate
- run: chmod +x queen-migrate
- run: ./queen-migrate validate
- run: ./queen-migrate plan
- run: ./queen-migrate up --yes
- run: ./queen-migrate check --ci --no-gaps --json
- run: ./queen-migrate status

Use a deployment environment with approval rules when the production migration step needs manual review. GitHub artifact downloads do not preserve executable permissions, which is why the deploy job runs chmod +x.

Make the application rollout depend on the deploy-migrations job. Keep schema changes compatible with both old and new application instances during a rolling deployment. The migration job should run once per environment; PostgreSQL advisory locks guard against accidental concurrent runs.

check --ci belongs after up: it fails if the release still has pending migrations. Use validate and plan before up. check --rollback-test applies, resets, and reapplies migrations, so point it only at a disposable database.

For machine-readable CI output, run queen-migrate check --ci --json. It emits one JSON object with passed, failed, exit_code, and output; the process exits with the same nonzero code on failure. Configuration errors use code 2, validation and connectivity errors use 3, strict gap failures use 4, pending migrations use 5, and rollback test failures use 6.

If --json is combined with --rollback-test, Go migration callbacks should write diagnostics to stderr or the configured logger. Printing directly to stdout from a callback can mix application output with the JSON result.

queen create adds new files to the conventional migrations/migrations.go registry generated by queen init. Custom registries still require manual registration. verify-registry checks Go source declarations with literal versions in the package job while the sources are available. It rejects a binary whose registered migration versions differ from the source directory, including a migration file that was created but never added to Register. Go-function migrations also need ManualChecksum; bump it when changing the function's behavior.