feat(deploy): apply migrations at api container start
`prisma migrate deploy` ran in one place only: a workflow step on the Gitea
runner, which has to reach the target host's MySQL on 3306 directly. Two
paths went around it:
- `skip_migrate=true`, the documented answer for when the runner cannot
reach 3306, left the schema a release behind with nothing to catch it.
The mismatch surfaced later as a column-not-found at runtime rather than
as a failed deploy.
- A container brought back by `restart: unless-stopped` after a host
reboot, or a stack re-applied by hand in Portainer, never runs the
workflow at all.
docker/api-entrypoint.sh becomes the api image's ENTRYPOINT: migrate, then
exec node. If the migration fails the container exits non-zero and the API
never listens — serving against a schema that does not match the code is
worse than being down, because the failures are partial and silent (a write
to a missing column breaks one feature while the rest looks healthy).
This does not replace the workflow step and is not a substitute for it. That
step still runs FIRST, while the old code is serving, which is the order
expand/contract migrations are designed around. `migrate deploy` is
idempotent, so on the normal path the container's run is a no-op query.
Behaviour:
RUN_MIGRATIONS=false skip and start anyway; plumbed through both app
stack files, for a schema moved by hand
DATABASE_URL unset refuse to start, and say why
P1001 (unreachable) retry, default 20 x 3s -- a cold db container, and
galactus's MagicDNS lookup right after a reboot
anything else exit at once; retrying a broken migration only
delays the same error. P3005 prints the
`migrate resolve --applied 0000_init` hint the
workflow step already printed.
Only P1001 retries, so a genuinely broken migration is not buried under a
minute of noise.
Both stacks are replicas: 1 and must stay so for an unrelated reason (the
servicios email sweep has no DB lock). The old comment claiming migrations
must not run per-container because "N replicas would race" is dropped: they
would not corrupt anything, since Prisma takes a database advisory lock and
the losers find nothing pending -- they would only each pay the wait.
The prisma CLI is already in the runtime layer (the image copies
/repo/node_modules wholesale), but which of the two plausible .bin paths
carries it is an implementation detail of pnpm's hoisted linker, so the
entrypoint accepts either and the Dockerfile asserts one exists at BUILD
time. A missing CLI breaks the image build, not a production boot.
Verified by running the entrypoint against stubbed prisma binaries: clean
run, P3005, P1001-to-exhaustion, P1001-then-recovery, RUN_MIGRATIONS=false,
missing DATABASE_URL, missing CLI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,12 @@
|
||||
# 3. prisma migrate deploy forward-only. Prisma has no down-migrations; see
|
||||
# docs/DEPLOY_AND_MIGRATIONS.md — expand/contract is
|
||||
# the rule, the backup is the emergency lever.
|
||||
# Done HERE so the schema moves while the OLD code is
|
||||
# still serving. The api container ALSO migrates at
|
||||
# start (docker/api-entrypoint.sh); `migrate deploy`
|
||||
# is idempotent, so the second run is a no-op and the
|
||||
# container is what covers a restart that never goes
|
||||
# through this workflow at all.
|
||||
# 4. app (api + web) the new images.
|
||||
# 5. verify ask the running API what it actually is.
|
||||
#
|
||||
@@ -55,10 +61,10 @@
|
||||
# uses until somebody saves them there
|
||||
# These are NOT galactus-specific (no _GALACTUS suffix) — one SES identity
|
||||
# serves every deployment.
|
||||
# - The runner (which lives on cubex) must be able to reach BOTH
|
||||
# galactus:9443 (Portainer) and galactus:3306 (MySQL, for migrate deploy).
|
||||
# If it cannot reach 3306, run the migration by hand from a host that can
|
||||
# and dispatch with skip_migrate=true.
|
||||
# - The runner (which lives on cubex) must be able to reach galactus:9443
|
||||
# (Portainer). It should also reach galactus:3306 for step 3, but that is
|
||||
# no longer load-bearing: dispatch with skip_migrate=true and the api
|
||||
# container applies the migrations itself at start.
|
||||
# - ONE-TIME, on a database that predates migration history (i.e. one built
|
||||
# with `prisma db push`): baseline it before the first run, or step 3 fails
|
||||
# with P3005 "database schema is not empty":
|
||||
@@ -88,7 +94,7 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
skip_migrate:
|
||||
description: "Skip prisma migrate deploy (use when the runner cannot reach MySQL and you migrated by hand)"
|
||||
description: "Skip the runner-side migrate step (safe: the api container migrates at start)"
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
@@ -237,6 +243,10 @@ jobs:
|
||||
run: node deploy/scripts/pre-migrate-backup.mjs
|
||||
|
||||
# --- schema, forward-only ---------------------------------------------
|
||||
# Belt to the container's braces: this runs while the OLD code is still
|
||||
# serving, which is the order expand/contract is designed around. The
|
||||
# api container repeats it at start for the paths this step cannot
|
||||
# reach (skip_migrate, a host reboot, a stack re-applied by hand).
|
||||
- name: Apply database migrations
|
||||
if: ${{ github.event.inputs.skip_migrate != 'true' }}
|
||||
env:
|
||||
|
||||
Reference in New Issue
Block a user