Run Database Migrations
Use lifecycle hooks to run Alembic, Prisma, Django, or Drizzle migrations on every deploy
Database migrations update your database schema — adding tables, columns, indexes, and more — as your application evolves. Hostess can run migration commands before each deployment using lifecycle hooks, so schema changes can move with your application code.
How It Works
init and shutdown run inside your service container — init before the main process starts, shutdown before it is terminated.
migrate and post_deploy run as separate background processes. They are submitted before your new service version is rolled out, but do not block the rollout — your service may start before they complete.
| Hook | Where | When |
|---|---|---|
init | Inside your service container | Before the main process starts |
migrate | Separate background process | Submitted before rollout; runs concurrently |
post_deploy | Separate background process | Submitted before rollout; runs concurrently |
shutdown | Inside your service container | Before the process is terminated |
Because migrate runs concurrently with your service rollout, write migrations to be backward-compatible — both the old and new versions of your code should tolerate the schema during the transition.
Framework-Specific Configuration
Alembic is the standard migration tool for SQLAlchemy-based Python applications (FastAPI, Flask, etc.).
version: "1.0"
services:
database:
type: postgres
resources: medium
api:
type: fastapi
build:
source: ./backend
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
migrate:
command: ["alembic", "upgrade", "head"]
timeout: 10mHow it works:
alembic upgrade headapplies all pending migration files from youralembic/versions/directory- The
DATABASE_URLenvironment variable is automatically available to the migration job - The migration runs from your application's build context, so your
alembic.iniand migration files are included
Creating new migrations locally:
# Generate a new migration based on model changes
alembic revision --autogenerate -m "add users table"
# Review the generated migration file in alembic/versions/
# Commit and deploy — Hostess runs it automaticallyPrisma is a popular ORM for Node.js and TypeScript applications.
version: "1.0"
services:
database:
type: postgres
resources: medium
api:
type: custom
build:
source: ./backend
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
migrate:
command: ["npx", "prisma", "migrate", "deploy"]
timeout: 5mHow it works:
prisma migrate deployapplies all pending migrations from yourprisma/migrations/directory- This command is safe for production — it only applies existing migration files (unlike
prisma migrate devwhich also creates new ones) - Prisma reads
DATABASE_URLfrom the environment automatically
Creating new migrations locally:
# Generate a new migration based on schema changes
npx prisma migrate dev --name add_users_table
# This creates a migration file in prisma/migrations/
# Commit and deploy — Hostess runs it automaticallyDjango includes a built-in migration system as part of its ORM.
version: "1.0"
services:
database:
type: postgres
resources: medium
web:
type: custom
build:
source: .
command: ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
ports: [8000]
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
init:
command: ["python", "manage.py", "collectstatic", "--noinput"]
migrate:
command: ["python", "manage.py", "migrate", "--noinput"]
timeout: 10mHow it works:
python manage.py migrate --noinputapplies all pending migrations across all Django apps- The
--noinputflag prevents interactive prompts (important for automated deployments) - The
inithook runscollectstaticbefore migrations — both complete before the app starts
Creating new migrations locally:
# Generate migrations for model changes
python manage.py makemigrations
# Review the generated files in your app's migrations/ directory
# Commit and deploy — Hostess runs it automaticallyDrizzle is a lightweight TypeScript ORM with a migration toolkit.
version: "1.0"
services:
database:
type: postgres
resources: medium
api:
type: custom
build:
source: .
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
migrate:
command: ["npx", "drizzle-kit", "migrate"]
timeout: 5mHow it works:
npx drizzle-kit migrateapplies all pending SQL migration files from yourdrizzle/directory- Drizzle reads the database connection from your
drizzle.config.ts(which should useDATABASE_URL)
Creating new migrations locally:
# Generate migration files from schema changes
npx drizzle-kit generate
# Review the generated SQL in drizzle/
# Commit and deploy — Hostess runs it automaticallyFor any other migration tool (Flyway, Liquibase, golang-migrate, etc.), provide the command directly:
services:
api:
type: custom
build:
source: .
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
migrate:
command: ["./migrate.sh"]
timeout: 10mOr with a multi-part command:
lifecycle:
migrate:
command: ["sh", "-c", "migrate -path ./migrations -database $DATABASE_URL up"]Make sure your migration tool and scripts are included in your Docker build context.
Timeout Configuration
Migrations have a default timeout of 5 minutes. For large databases or complex migrations, increase the timeout:
lifecycle:
migrate:
command: ["alembic", "upgrade", "head"]
timeout: 10m # 10 minutesSupported time formats:
30s— 30 seconds5m— 5 minutes (default)10m— 10 minutes1h— 1 hour
If the migration exceeds the timeout, the migration job exceeds its active deadline and is treated as failed.
Combining Lifecycle Hooks
You can use multiple lifecycle hooks together. They execute in order:
services:
web:
type: custom
build:
source: .
depends_on:
- database
env:
DATABASE_URL: ${database.url}
lifecycle:
init:
command: ["python", "manage.py", "collectstatic", "--noinput"]
migrate:
command: ["python", "manage.py", "migrate", "--noinput"]
timeout: 10m
post_deploy:
command: ["python", "scripts/warmup_cache.py"]
shutdown:
timeout: 30s
command: ["python", "scripts/cleanup.py"]Execution order:
initruns inside your service container before the main process starts (collect static files)migrateandpost_deployare submitted as background processes before the rollout — they run concurrently with the service startingshutdownruns inside your service container when it is being terminated
Best Practices
Write reversible migrations
Whenever possible, write migrations that can be rolled back. Most migration tools support "down" migrations:
- Alembic:
alembic downgrade -1 - Prisma: Prisma doesn't support down migrations natively — test thoroughly before deploying
- Django:
python manage.py migrate app_name 0003_previous_migration - Drizzle:
npx drizzle-kit drop
Test migrations on a preview environment first
Before running a migration in production, deploy to a preview or staging environment:
# Deploy to staging first
hostess deploy --env staging
# Verify the migration succeeded, then deploy to production
hostess deploy --env productionAvoid long-running migrations in a single step
Large data migrations (e.g., backfilling millions of rows) can time out. Break them into smaller steps:
- Schema migration: Add the new column (fast, runs during deploy)
- Data migration: Backfill data in batches using a post-deploy script or background job
- Cleanup migration: Remove the old column once backfill is complete
Keep migration files in version control
Always commit your migration files alongside the code that uses the new schema. This ensures that:
- Migrations are reviewed in pull requests
- Every deployment has the correct set of migrations
- You can track schema changes over time