DevOps

Building CI/CD Pipelines That Actually Work

Syntaxcape Engineering|2026.02.24|~7 min read
CI/CDGitHub ActionsDockerdeployment

A pipeline that works in a demo is not the same as a pipeline that works at 2 AM when a critical hotfix needs to ship. We have seen teams invest weeks building elaborate CI/CD configurations that collapse the moment someone pushes a database migration alongside a feature branch. The fix is not more tooling — it is better design.

The three-gate model

Every pipeline we build follows a three-gate model: lint and type-check (fast feedback), test (comprehensive validation), and deploy (environment-specific). Each gate is independently retriable. If tests pass but deploy fails due to a transient cloud error, the team reruns only the deploy stage instead of waiting 12 minutes for the full pipeline to repeat.

yaml
# .github/workflows/deploy.yml
jobs:
  gate-1-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm lint && pnpm typecheck
  gate-2-test:
    needs: gate-1-lint
    runs-on: ubuntu-latest
    steps:
      - run: pnpm test:ci
  gate-3-deploy:
    needs: gate-2-test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: pnpm deploy:production

Database migrations as first-class citizens

Migrations run in a dedicated step before the application deploy. They are tested against a shadow database in CI, and every migration file includes a corresponding rollback. We have seen too many teams treat migrations as an afterthought — that approach works until it does not, usually on a Friday evening.

  • Run migrations before application deploy, not during startup
  • Test every migration against a shadow database
  • Include rollback scripts for every forward migration
  • Version-lock your migration tool alongside your ORM
// reactions
[Related]

Continue reading.

/Support

Have a technical challenge? We’d love to help.

>