ci(release): apply pending migrations before publishing the image - #230
Merged
Conversation
Nothing in the pipeline ran migrations. release.yml cut a version and pushed an image; the Dockerfile starts the server directly; db:deploy existed in package.json with no caller. Applying migrations was a manual step somebody had to remember. It was missed for the notification deep-link columns, so production ran code that selects notifications.postId, articleId and commentId against a database without them. GET /notifications answered 500 with a PrismaClientKnownRequestError, and every write that creates a notification was in the same position. A migrate job now runs between the release and the image push, so the schema is in place before the image that expects it can be pulled. It uses deploy, which only applies what has not run yet and takes an advisory lock, and it prints migrate status first so the run log records what each release applied. A missing DATABASE_URL secret fails the job with a message that names the secret rather than a Prisma error. The job deliberately does not set NODE_ENV=production: pnpm would skip devDependencies, and the Prisma CLI it runs is one of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwzkQ5YGFXSB9jWCZKzX4H
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DATABASE_URLrepository secretThe new job fails loudly without it, and it gates the image push. That is deliberate — an image whose schema has not been applied is the bug this PR exists to prevent — but it means the next release will not publish until the secret is there.
And this PR does not fix production. It stops the next occurrence. The current outage still needs
pnpm prisma migrate deployrun against the production database once, by hand.Why
Nothing in the pipeline ever ran migrations:
release.ymlcuts a version, then pushes an image to GHCRDockerfilestarts withCMD ["node", "dist/index.js"]db:deploysits inpackage.jsonwith no caller anywhereSo applying a migration was a manual step somebody had to remember, and the only thing that told you it had been forgotten was production breaking.
Which is what happened.
20260826233221_notification_deep_link_targetsshipped with v1.8.0 (#221) but was never applied, so the deployed code selectsnotifications.postId,articleIdandcommentIdfrom a database that does not have them:{ "type": "about:blank", "title": "PrismaClientKnownRequestError", "status": 500, "detail": "The server could not complete the request.", "instance": "/api/v1/notifications?page=1&limit=20" }GET /notificationsis just the loudest symptom — every write that creates a notification touches the same columns, so follows, likes and comments were all in the same position.What the job does
Runs after the release and before the image push, so the schema is in place before the image that expects it can be pulled:
prisma migrate deploy— only applies what has not run yet, never rewrites or drops anything, and takes an advisory lock so overlapping releases queue rather than raceprisma migrate statusruns first withcontinue-on-error, so the run log records what each release applied. It exits non-zero exactly when something is pending, which is the normal case here, so its status is not the job'sDATABASE_URLfails with::error::The DATABASE_URL secret is not set…rather than a Prisma error that does not name the causeenvironment: productiongives the run a deployment record, and somewhere to add required reviewers if this should ever need approvalOne trap worth knowing about
The job deliberately does not set
NODE_ENV=production. pnpm skipsdevDependencieswhen it sees that, and the Prisma CLI is a devDependency — the job would have failed atpnpm prisma. The datasource URL does not need it either:prisma.config.tsreadsprocess.env.DATABASE_URL, and the.envfile it looks for does not exist on a runner, so dotenv is a no-op and the step env wins.Verification
The workflow parses and the job graph is what it should be:
The job itself can only really be exercised by a release, so it will prove itself on the next merge to
main— with the secret in place.🤖 Generated with Claude Code
https://claude.ai/code/session_01PwzkQ5YGFXSB9jWCZKzX4H