← Back to Blog
March 16, 2026 infrastructure 3 min read

Zero-Downtime Migrations: Keeping the Engine Running

If updating your schema forces you to put up a "maintenance mode" banner, your deployment strategy is obsolete. Here is how to orchestrate seamless updates.

deployment postgresql migrations coolify

The Fear of the Live Database

One of the most terrifying moments for a development team is deploying a database schema change to a live, high-traffic system. If you alter a table that is actively being written to by hundreds of concurrent users, you risk locking the table, dropping queries, and crashing the API.

For most agencies, the solution is the archaic "maintenance window." They send an email to clients, put up a 503 "We'll be right back" banner at 2:00 AM on a Sunday, and pray the migration script runs cleanly. If your architecture requires you to take the business offline to push code, your architecture is obsolete.

Modern sovereign infrastructure demands zero-downtime migrations. Your clients should never know you are updating the database. The engine must stay running while you rebuild the car.

⚠ Warning

Never rename a column or drop a column in a single deployment step. It will instantly break the currently running API containers that expect the old schema.

The Expand and Contract Pattern

To achieve zero downtime, we never execute destructive database actions while the old code is running. We use a multi-step orchestration technique known as the Expand and Contract pattern.

Let's say you want to change a column name from full_name to two separate columns: first_name and last_name. If you just drop full_name, the live API fails. Here is the strict sequence:

  • Deploy 1 (Expand): Add the new columns (first_name, last_name) to the database. They are empty. The live API ignores them because it doesn't know they exist.
  • Deploy 2 (Dual Write): Update the FastAPI code. When a new user registers, the API writes to the old full_name column AND the new first_name/last_name columns.
  • Background Script: Run a background task to backfill the historical data, splitting the old full_name data into the new columns for all past users.
  • Deploy 3 (Read Switch): Update the API to read exclusively from the new first_name and last_name columns. The system is now fully utilizing the new schema.
  • Deploy 4 (Contract): Drop the old full_name column. The migration is complete. The system never went offline.

Alembic and FastAPI

We do not run SQL scripts manually. In my architecture, we manage migrations programmatically using Alembic integrated with SQLAlchemy and asyncpg. Alembic generates version-controlled migration files that live in the Git repository alongside the API code.

# Alembic Migration Example (Expand phase)
def upgrade():
    # Add the new columns safely. No destructive actions.
    op.add_column('users', sa.Column('first_name', sa.String(), nullable=True))
    op.add_column('users', sa.Column('last_name', sa.String(), nullable=True))

def downgrade():
    op.drop_column('users', 'first_name')
    op.drop_column('users', 'last_name')

Coolify Deployment Routing

The Expand and Contract pattern handles the database safely, but how do we switch the API code without dropping requests? This is where Coolify proves its value as a self-hosted PaaS.

When you push the new FastAPI code to GitHub, Coolify builds the new Docker container in isolation. The old container continues serving live traffic. Once the new container passes its internal health checks (e.g., confirming it can connect to the database), Coolify commands the Traefik reverse proxy to route all new incoming traffic to the new container.

✓ Container api-v2.0.1 built successfully
✓ Health check passed on port 8000
[Traefik] Routing traffic to api-v2.0.1
[Traefik] Draining connections from api-v2.0.0
[Docker] Stopping container api-v2.0.0

The old container gracefully finishes processing any ongoing requests before shutting down. Not a single webhook is dropped. Not a single user sees a 502 Bad Gateway error.

Boring is Profitable

Deployments should not be adrenaline-fueled events. They should be boring, predictable, and fully automated. By combining programmatic database migrations with proxy-level container routing, you cultivate an engineering culture that ships fearlessly and scales infinitely.

Start Your Moat Audit ← Back to all posts

// Related Posts

Mar 16, 2026

Anti-Pattern: The Monolithic Deployment Trap

Failure pattern #3: Monolithic deploys. If one bug in a minor feature takes down the entire site, your architecture is flawed. Move to modular deployments, distinct APIs, and decoupled frontends to isolate blast radiuses.

Mar 16, 2026

Escaping the Zapier Tax: Why I Self-Host n8n

Zapier's per-task pricing scales directly with your success—punishing you for growing. By self-hosting n8n, you pay a flat server cost for a visual workflow automation engine that runs 24/7. You own the infrastructure. You own the logic.

Mar 16, 2026

Coolify: Taking Back Infrastructure Sovereignty

Stop overpaying for AWS or Heroku. I use Coolify to run a self-hosted PaaS on a $50/mo VPS. Git push deploys, automatic SSL, Docker containers, and database management—all with zero cloud provider lock-in.

← PreviousVector Search in Postgres: Preparing Your Data for AI