Exercise 3: Migrations in CI and Deployment — Possible Solution ================================================================= .github/workflows/ci.yml name: CI on: [pull_request] jobs: schema-check: runs-on: ubuntu-latest services: postgres: image: postgres:17 env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: blog_test ports: ["5432:5432"] options: >- --health-cmd "pg_isready -U test" --health-interval 5s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgresql://test:test@localhost:5432/blog_test steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 22 } - run: npm ci - run: npx prisma validate - name: Fail if schema.prisma has changes with no migration run: | npx prisma migrate diff \ --from-migrations prisma/migrations \ --to-schema prisma/schema.prisma \ --exit-code - name: Migrations apply cleanly to an empty database run: npx prisma migrate deploy - run: npx prisma generate - run: npm test "migrate diff --exit-code" exits with 0 when nothing differs, 2 when it finds differences and 1 on an error, so either failure stops the job. Deploy (a separate release step, run once per deployment): deploy: steps: - run: npm ci - run: npx prisma migrate deploy env: DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }} - run: ./start-rollout.sh # only after migrations succeeded Many platforms have a "release" or "pre-deploy" command for exactly this (run once, before new instances start). Avoid putting "migrate deploy" in the app's own start command when several copies start at once. WHY THIS WORKS AS AN ANSWER ------------------------------ The pull-request job catches the most common mistake (a schema change without a migration) and also proves every migration applies to a fresh database. The deploy step runs migrations exactly once, before the new code starts, and the rollout depends on it succeeding — so new code never runs against an old schema. Production credentials are only available to the deploy step, never to pull requests.