Run database migrations as part of the deploy
Deploy / deploy (push) Successful in 3m45s

The deploy workflow pulled, built and started the app, but never applied
migrations. Shipping a change that adds a table therefore broke the app
until someone remembered to migrate by hand -- the new api_keys table
would have done exactly that to the settings page.

The workflow now builds, applies pending migrations, and only then starts
the app. A failed migration exits non-zero, so the deploy fails instead of
starting an app whose schema is behind its code. Because `up -d` is never
reached in that case, production keeps serving the previous containers.

Migrations run through a new `migrate` compose service built from the
Dockerfile's builder stage, rather than `docker compose exec app npx
prisma migrate deploy` as the README previously suggested. The production
image copies prisma/ and node_modules/.prisma but not the Prisma CLI,
which is a devDependency and is absent from the standalone bundle, so that
command would have had npx download Prisma inside the container at runtime
as the unprivileged nextjs user. The builder stage has the CLI and its
engines already.

The service sits behind a `tools` profile so `docker compose up` never
starts it, and takes DATABASE_URL from compose rather than a .env file --
dotenv does not override variables that are already set, so the compose
value wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
twotalesanimation
2026-11-11 09:30:58 +02:00
parent 8a93a18bb9
commit 396ebf9837
3 changed files with 53 additions and 4 deletions
+12 -1
View File
@@ -20,6 +20,17 @@ jobs:
git pull
docker compose up -d --build
# Build both images up front. `migrate` targets the Dockerfile's
# builder stage, which `app` also builds through, so the layers are
# shared and this costs no extra build time. The profile is named
# explicitly rather than relying on a service name activating it.
docker compose --profile tools build app migrate
# Apply pending migrations before the new app serves traffic. This
# exits non-zero if a migration fails, failing the deploy rather than
# starting an app whose schema is behind its code.
docker compose --profile tools run --rm migrate
docker compose up -d
docker image prune -f
+13 -3
View File
@@ -112,11 +112,21 @@ Open http://localhost:3000
docker compose up --build -d
```
After first run, apply migrations:
Pushing to `main` triggers the Gitea workflow in `.gitea/workflows/deploy.yml`,
which pulls, builds, **applies pending migrations, and only then starts the app**.
A failed migration fails the deploy rather than starting an app whose schema is
behind its code, so there is no manual migration step.
Migrations run through the `migrate` service, which builds from the Dockerfile
`builder` stage. That matters: the production image ships runtime dependencies
only, and the Prisma CLI is a devDependency, so `docker compose exec app npx
prisma ...` would try to download Prisma inside the container at runtime.
To run migrations by hand (or to seed on a first deploy):
```bash
docker compose exec app npx prisma migrate deploy
docker compose exec app npx prisma db seed
docker compose --profile tools run --rm migrate
docker compose --profile tools run --rm migrate npx prisma db seed
```
---
+28
View File
@@ -21,6 +21,34 @@ services:
networks:
- xbid
# One-off migration runner, applied by the deploy workflow before the app
# comes up. It builds from the Dockerfile's `builder` stage because the
# production image ships runtime dependencies only -- the Prisma CLI and its
# engines are devDependencies and are not in the standalone bundle.
#
# Behind a profile so `docker compose up` never starts it; the deploy targets
# it explicitly with `docker compose run --rm migrate`.
migrate:
build:
context: .
dockerfile: Dockerfile
target: builder
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/xbid
command: ["npx", "prisma", "migrate", "deploy"]
depends_on:
postgres:
condition: service_healthy
networks:
- xbid
profiles:
- tools
app:
build:
context: .