Setting up Development:

1. Always Check .dockerignore

Check if you have a .dockerignore file in your project root.
If it contains a line: migrations/,
— Docker will skip copying that folder during the COPY . . step. 
Remove it and rebuild:

docker compose build web

2. The “Clean Slate” Initialization

Run these three commands in order:

# 1. Create the migrations folder structure
docker exec -it saas_app flask db init

# 2. Scan your models and generate the "Create Table" scripts
docker exec -it saas_app flask db migrate -m "initial migration"

# 3. Apply those scripts to the Postgres database
docker exec -it saas_app flask db upgrade

3. Resync back to source repository:

After completing database initialization,
copy the generated migration folder back to DEV machine repository:

docker cp saas_app:/app/migrations ./migrations

That way, you don’t lose your migration history when you delete the container.

  • Once database initialization has been completed on production server:
    • Never run flask db init or flask db migrate on Production Server.

Ongoing Future development

When you move from work from Dev to Prod,
this process allows new features to be deployed without breaking live data.

1. The Dev Side (Developer’s Machine)

Never run flask db init or flask db migrate on Production.

  • Modify your models.py.
  • Run: flask db migrate -m "add_phone_to_user" locally.
  • Crucial: Inspect the generated file in migrations/versions/. Ensure it looks correct.
  • Commit the models.py and the new script in migrations/versions/ to Git.

2. The Prod Side (Server)

When you pull the new code to your server:

  • Rebuild the Image: Since your code changed, you need a new build:
    • docker compose build web
  • Restart the Stack:
    • set -a; source .env; set +a; docker compose up -d
  • The Auto-Upgrade:
    Because flask db upgrade in your docker-compose.yml command string,
    the container will automatically see the new version file you pushed and apply it to the Postgres database before Gunicorn starts.

3. Handling the seed.py in Prod

Developer a seed.py to “Check if records exists, then insert,” that way it is safe to run on every deployment.
However, as your app grows,
you might want to separate System Seed (Roles, Statuses) from Dev Seed (Test Users).

  • System Data: Keep it in the automated startup command.
  • Dev Data: Only run it manually when you need dummy data.

Summary Checklist for “Promotion”

ActionLocationCommand
Create MigrationDevflask db migrate -m "description"
Commit to GitDevgit add . && git commit -m "schema change"
Pull ChangesProdgit pull
Build & DeployProddocker compose up -d --build
VerifyProddocker compose logs -f web (Check for “No module named…” or “Upgrade successful”)

Development Promotion Walkthrough:

If you run multiple migrations in DevFlask-Migrate (Alembic) treats them like a “chain of commits” for your database. Each migration file in migrations/versions/ has an ID and a “parent” ID.

When you promote these to Prod, here is exactly how it behaves:

1. The “Catch-Up” Effect

If Prod is on Migration A, and you created Migration B, C, and D in Dev, running flask db upgrade in Prod will:

  • Detect that it is currently at version A.
  • See that B follows A, so it runs B.
  • See that C follows B, so it runs C.
  • See that D follows C, so it runs D.
  • Result: Prod is now perfectly synced with Dev. You don’t have to run them one by one; upgrade handles the whole chain.

2. The “Branching” Danger ⚠️

Never run flask db migrate on Prod.

  • If you run migrate on Prod, it creates a new migration file on the server that isn’t in your Git repo.
  • When you later push a migration from Dev,
    Prod will see two different “next” steps and throw a Conflicting migrations error.
  • Rule: Always migrate in Dev, always upgrade in Prod.

3. Cleaning Up “Migration Mess”

If you’ve been experimenting in Dev and have 10 tiny migration files
(e.g., “fix typo”, “add column”, “oops fix typo again”),
it’s best to squash them before pushing to Prod:

  1. In Dev, delete the messy files in migrations/versions/.
  2. Drop your local Dev database (or just the alembic_version table).
  3. Run flask db migrate -m "feature_name_complete" to create one clean file.
  4. Push that single clean file to Prod.

4. How to Verify Prod’s Status

If you’re ever unsure what version your container database is currently on, run:

docker exec -it saas_app flask db current

And to see what migrations are available but not yet applied:

docker exec -it saas_app flask db history

Pro-Tip: 
If a migration fails halfway through on Prod
(e.g., trying to add a NOT NULL column to a table that already has data),
the database might get stuck.

In conclusion:

Always take that pg_dump backup before pushing schema changes!

# Create a quick SQL dump just in case
docker exec saas_db pg_dump -U ${DB_USER} ${DB_NAME} > pre_migration_backup.sql

Leave a Reply