fix: run migrations via Next.js instrumentation.ts at startup

tsx v4 + Node.js 22 ESM does not handle extensionless .ts imports.
instrumentation.ts uses compiled/bundled code — no resolution issues.
Migrations run automatically before first request on every app start.
Removes external migrator container approach entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-02-23 14:14:18 +00:00
parent 1348188a6c
commit 4e23350bfc
3 changed files with 29 additions and 16 deletions

View file

@ -255,9 +255,21 @@ start_containers() {
run_migrations() {
header "Phase 5 — Database migrations"
log "Running Payload CMS migrations (via build stage — has pnpm + full node_modules)..."
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" --profile migrate run --rm migrator
log "Migrations complete."
# Migrations run automatically via src/instrumentation.ts on Next.js startup.
# We wait for the app container to be healthy (confirming startup completed).
log "Waiting for app container to pass health check (migrations run at startup)..."
local max_wait=120
local elapsed=0
while ! docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T app \
node -e "process.exit(0)" >/dev/null 2>&1; do
if [ "$elapsed" -ge "$max_wait" ]; then
warn "App health check timed out — check logs: docker compose -f $COMPOSE_FILE logs app"
return
fi
sleep 3
elapsed=$((elapsed + 3))
done
log "App is running. Migrations were applied at startup via instrumentation.ts"
}
# =============================================================================

View file

@ -12,19 +12,6 @@ services:
db:
condition: service_healthy
migrator:
build:
context: .
target: migrator
command: pnpm payload migrate
env_file:
- .env.production
depends_on:
db:
condition: service_healthy
profiles:
- migrate
db:
image: postgres:17-alpine
restart: always

14
src/instrumentation.ts Normal file
View file

@ -0,0 +1,14 @@
// Runs on Next.js startup (Node.js runtime only) before any requests are handled.
// Uses compiled/bundled code — no tsx/ESM resolution issues.
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { getPayload } = await import('payload');
const { default: config } = await import('@payload-config');
const payload = await getPayload({ config });
console.log('[startup] Running database migrations...');
await payload.db.migrate();
console.log('[startup] Migrations complete.');
}
}