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 initorflask db migrateon Production Server.
- Never run
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.pyand the new script inmigrations/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:
Becauseflask db upgradein yourdocker-compose.ymlcommand 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”
| Action | Location | Command |
|---|---|---|
| Create Migration | Dev | flask db migrate -m "description" |
| Commit to Git | Dev | git add . && git commit -m "schema change" |
| Pull Changes | Prod | git pull |
| Build & Deploy | Prod | docker compose up -d --build |
| Verify | Prod | docker compose logs -f web (Check for “No module named…” or “Upgrade successful”) |
Development Promotion Walkthrough:
If you run multiple migrations in Dev, Flask-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;
upgradehandles the whole chain.
2. The “Branching” Danger ⚠️
Never run flask db migrate on Prod.
- If you run
migrateon 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 aConflicting migrationserror. - Rule: Always
migratein Dev, alwaysupgradein 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:
- In Dev, delete the messy files in
migrations/versions/. - Drop your local Dev database (or just the
alembic_versiontable). - Run
flask db migrate -m "feature_name_complete"to create one clean file. - 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
