devops
The CI/CD chain, and 'green' meaning nothing
The CI/CD chain, and "green" meaning nothing
Not one monolithic pipeline — a chain of purpose-specific workflows triggering off each other:
ci.ymlon every push: backend lint, typecheck, migrations, schema drift, unit tests; a separate pgBouncer smoke job; frontend typecheck, lint, build; a full Playwright E2E job — parallel where possible, with anotifyjob depending on all of them, reporting pass/fail regardless of outcome.build-push.ymlafter CI succeeds on main, pushing images to the registry.deploy-production.ymlafter a successful build-push — pulls the images, runsprisma migrate deploy, starts the stack, health-checks it, rolls back automatically on failure.rollback-production.yml— a manual workflow for rolling back after the automated check already passed but a problem surfaces later.
Chaining with workflow_run rather than one giant job means each stage can be re-run or debugged on its own.
Self-hosted runners
Both CI and production deploy run on self-hosted runners. Two reasons: cost, and the fact that the production runner is the production VM — so "run commands on the runner" doubles as "run these Docker commands on the server that needs to end up running the new containers," with no separate SSH deploy step. The trade is being responsible for keeping that machine alive and patched; if the runner goes down, CI silently stops until someone fixes the runner itself.
concurrency:
group: deploy-production
cancel-in-progress: false
Only one production deploy at a time — a second trigger queues rather than racing the first, because two simultaneous docker compose up against the same stack is a genuinely bad time. cancel-in-progress: false means a new deploy waits rather than killing the current one mid-flight.
The round I actually remember
Standing this up took seven rounds in a single day. After everything went green, nothing had been built. The workflow_run chaining was silently broken by design under the conditions those jobs ran in — it just didn't run them, and didn't complain. I'd "verified" the pipeline by reading the workflow files, which it turns out is worth approximately nothing. Merged the build jobs into the main pipeline instead of chaining.
Second time in this project that "the config looks right" and "the thing actually happened" turned out to be different claims.
One more choice: notifications fire on success too, not just failure. For a solo developer, a confirmed "deploy succeeded" notification is what lets you actually walk away instead of anxiously refreshing the Actions tab.