feat(server): self-applying migrations on container start; bump v0.1.1
CI / secrets-scan (push) Successful in 5s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 14s
CI / test (push) Successful in 26s
CI / lint (push) Successful in 27s
CI / build-images (push) Successful in 3m38s
CI / image-scan (push) Successful in 24s
CI / push (push) Successful in 22s

Three coordinated changes so the deployed image is operationally
ready to roll into prod:

1. Keep Prisma CLI in the production bundle. Moved `prisma` from
   devDependencies into dependencies in packages/server/package.json
   so `pnpm deploy --prod` includes it. Version pin matches
   @prisma/client (^5.20.0) so the CLI's bundled engine and the
   client's stay aligned.

2. Container entrypoint that runs migrations before booting.
   Added packages/server/docker/entrypoint.sh: it invokes
   `/app/node_modules/.bin/prisma migrate deploy` against the
   live DATABASE_URL, then `exec "$@"` drops into the CMD
   (node dist/main.js). `migrate deploy` is the production-safe
   verb — only applies committed migrations from prisma/migrations,
   never generates new ones, idempotent on already-applied state.

   Invoked through tini in ENTRYPOINT so SIGTERM still flows
   cleanly to Node when the container is stopped. Uses the
   `.bin/prisma` symlink directly because we deleted npm/npx
   from the runtime image earlier (for the trivy gate).

   DATABASE_URL is redacted in the entrypoint's log line so the
   credentials don't end up in container logs.

3. Version bump: both packages 0.1.0 → 0.1.1 so Harbor's
   "refuse to overwrite existing tag" check accepts the push.

Verification:
- prisma CLI present at /app/node_modules/.bin/prisma in the
  runtime image.
- Empty DB boot: entrypoint applies 20260517191555_init,
  creates users + saves + _prisma_migrations tables, server
  reaches "listening", /api/health returns ok.
- Already-applied DB boot: entrypoint logs "No pending
  migrations to apply", server boots, no-op as expected.
- 80 unit tests + 7 integration tests pass.
- Trivy: 0 new findings; only the pre-existing lodash CVE
  remains (already in .trivyignore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 18:07:11 -07:00
parent 781356a254
commit 9ee4869042
5 changed files with 44 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@teh-riehl/client",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"type": "module",
"scripts": {
+9 -2
View File
@@ -79,6 +79,12 @@ COPY --from=builder /deploy/dist ./dist
COPY --from=builder /deploy/prisma ./prisma
COPY --from=builder /deploy/package.json ./package.json
# Entrypoint runs `prisma migrate deploy` before the server boots so an
# image rolled to prod is self-applying — no separate migration job needed.
# `migrate deploy` is idempotent: applies only committed migrations, never
# generates new ones.
COPY --chmod=755 packages/server/docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
# Drop privileges. The `node` user ships with the upstream image.
RUN chown -R node:node /app
USER node
@@ -88,6 +94,7 @@ ENV PORT=3000
EXPOSE 3000
# tini = PID 1 with proper signal forwarding so SIGTERM cleans up the Node
# process (Prisma keeps DB connections open otherwise).
ENTRYPOINT ["/sbin/tini", "--"]
# process (Prisma keeps DB connections open otherwise). The entrypoint
# script runs migrations, then `exec "$@"` drops into CMD (node).
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD ["node", "dist/main.js"]
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# =============================================================================
# Server container entrypoint.
#
# Runs `prisma migrate deploy` before booting the Nest app. `migrate deploy`
# is the production-safe verb: it only applies migrations already committed
# in /app/prisma/migrations/, never generates new ones, never resets data.
# Idempotent — if every migration is already applied, this exits 0.
#
# Invoked through tini (see Dockerfile ENTRYPOINT) so SIGTERM still flows
# cleanly to Node when the container is stopped.
# =============================================================================
set -eu
# We removed npm/npx in the runtime stage to keep the image small and the
# trivy gate green, so call prisma directly via its bin symlink.
PRISMA=/app/node_modules/.bin/prisma
SCHEMA=/app/prisma/schema.prisma
if [ ! -x "$PRISMA" ]; then
echo "[entrypoint] FATAL: prisma CLI missing at $PRISMA — check that prisma is a runtime dep (not dev)" >&2
exit 1
fi
echo "[entrypoint] applying prisma migrations against ${DATABASE_URL%%@*}@<redacted>"
"$PRISMA" migrate deploy --schema="$SCHEMA"
echo "[entrypoint] starting server: $*"
exec "$@"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@teh-riehl/server",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"scripts": {
"build": "nest build",
@@ -32,6 +32,7 @@
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"prisma": "^5.20.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
@@ -45,7 +46,6 @@
"@types/passport-local": "^1.0.38",
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^2.1.2",
"prisma": "^5.20.0",
"supertest": "^7.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
+3 -3
View File
@@ -181,6 +181,9 @@ importers:
passport-local:
specifier: ^1.0.0
version: 1.0.0
prisma:
specifier: ^5.20.0
version: 5.22.0
reflect-metadata:
specifier: ^0.2.2
version: 0.2.2
@@ -215,9 +218,6 @@ importers:
'@vitest/coverage-v8':
specifier: ^2.1.2
version: 2.1.9(vitest@2.1.9(@types/node@22.19.19)(jsdom@25.0.1)(terser@5.47.1))
prisma:
specifier: ^5.20.0
version: 5.22.0
supertest:
specifier: ^7.0.0
version: 7.2.2