Migrations update the PostgreSQL schema. Run them before starting API and worker processes that depend on a new release. The migration runner tracks applied migrations and applies only pending changes; it does not recreate the database each time it runs.
Docker Compose
The production stack includes a one-shot migrate service. Both the API and
worker wait for it to finish successfully:
docker compose --env-file .env.production -f compose.production.yaml up -d --build --wait
An exited migration container is expected after success. Inspect its logs when startup is blocked:
docker compose --env-file .env.production -f compose.production.yaml logs migrate
Other hosting platforms
Run the image's migrate command as a release job before starting the new API
and worker release. The same command is used by Compose and can be configured
as a deployment step on your chosen host.
Provide DATABASE_URL and, where required, DATABASE_URL_DIRECT to that job.
The runner uses a database advisory lock so concurrent migration attempts are
serialized, then releases the lock when the process finishes.
Pooled database connections
If the runtime uses a Neon pooled endpoint, the migration runner requires
DATABASE_URL_DIRECT pointing to its direct endpoint. The supplied Compose
migrate service forwards DATABASE_URL only. When adapting it for managed
PostgreSQL, explicitly pass DATABASE_URL_DIRECT to the migration service too.
Updating safely
Back up PostgreSQL before upgrading and review the release's schema changes. If a migration fails, inspect the failure before starting application processes. Do not delete database volumes or assume reverting the application image also reverses schema changes.