flowCreate.solutions

Production Migration Runner

This document provides an example run_prod_migrations.py wrapper script. It runs Alembic commands against a production database with additional guardrails.

When to Use

  • Apply already-reviewed migrations in production (upgrade head).
  • Inspect current revision and history (current, history).

Avoid using production runners for creating revisions. Revisions should be created and reviewed in a shared dev/staging workflow first.

Usage

# Safe read-only commands (no confirmation required)
PYTHONPATH=. python migrations/run_prod_migrations.py current
PYTHONPATH=. python migrations/run_prod_migrations.py history

# Mutating commands (explicit confirmation required)
PYTHONPATH=. python migrations/run_prod_migrations.py upgrade head --yes
PYTHONPATH=. python migrations/run_prod_migrations.py stamp head --yes

Script Implementation

Save this file as migrations/run_prod_migrations.py.

import os
import subprocess
import sys
import logging
from dotenv import load_dotenv

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("migrations.run_prod_migrations")

load_dotenv(override=True)

APP_ENV = "production"
os.environ["ENVIRONMENT"] = APP_ENV

prod_url = os.getenv("DATABASE_URL_PROD")
if not prod_url:
    logger.error("DATABASE_URL_PROD is not set. Cannot continue.")
    sys.exit(1)

os.environ["DATABASE_URL"] = prod_url

if len(sys.argv) < 2:
    logger.info("Usage: python migrations/run_prod_migrations.py <alembic-args...> [--yes]")
    logger.info("Examples:")
    logger.info("  python migrations/run_prod_migrations.py current")
    logger.info("  python migrations/run_prod_migrations.py upgrade head --yes")
    sys.exit(1)

args = sys.argv[1:]
confirmed = False
if "--yes" in args:
    confirmed = True
    args = [a for a in args if a != "--yes"]

read_only = {"current", "history", "heads", "branches"}
command = args[0] if args else ""

if command not in read_only and not confirmed:
    logger.error("Refusing to run production mutating command without --yes. Command: %s", " ".join(args))
    sys.exit(2)

logger.info("Running Alembic in %s: alembic %s", APP_ENV, " ".join(args))
subprocess.run(["alembic", *args], check=True)