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:
@@ -113,5 +113,21 @@ ENV APP_VERSION=$APP_VERSION \
|
||||
GIT_SHA=$GIT_SHA \
|
||||
BUILD_DATE=$BUILD_DATE
|
||||
|
||||
# Pending migrations are applied at container start, before Nest listens —
|
||||
# see the header of the script for why this is done here as well as in the
|
||||
# deploy workflow. Asserted at BUILD time so a missing prisma CLI breaks the
|
||||
# image build rather than a production boot: the runtime layer copies
|
||||
# /repo/node_modules wholesale, and which of these two paths carries the bin
|
||||
# is an implementation detail of pnpm's hoisted linker.
|
||||
COPY docker/api-entrypoint.sh /usr/local/bin/api-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/api-entrypoint.sh
|
||||
RUN for c in /repo/node_modules/.bin/prisma \
|
||||
/repo/packages/database/node_modules/.bin/prisma; do \
|
||||
if [ -x "$c" ]; then echo "prisma CLI found at $c"; exit 0; fi; \
|
||||
done; \
|
||||
echo "FATAL: prisma CLI is not in the runtime layer; api-entrypoint.sh cannot migrate" >&2; \
|
||||
exit 1
|
||||
|
||||
EXPOSE 3001
|
||||
ENTRYPOINT ["/usr/local/bin/api-entrypoint.sh"]
|
||||
CMD ["node", "apps/api/dist/main.js"]
|
||||
|
||||
Reference in New Issue
Block a user