Migrations & deploy
Operational notes for running the WriteMars backend. Full deployment specifics
and the production checklist live in the repository's root README.md; this
page is the short runbook for migrations and the deploy shape. See
system architecture for the component overview.
How a Railway deploy runs
The backend ships as a Docker image (built from backend/Dockerfile). On
each deploy Railway:
- Builds the image.
- Runs
python manage.py migrate --noinputas thepreDeployCommand— a separate pre-cutover container, so schema changes land before any new code serves traffic (backend/railway.json). - Starts the web service via
/app/bin/docker-entrypoint-api.sh, which execs gunicorn servingwritemars.config.wsgi. (The entrypoint can also migrate on start, but on Railway that's disabled viaMIGRATE_ON_START=falsebecause the pre-deploy step already handled it.)
The worker and beat run as separate Railway services from the same
image, using railway-worker.json and railway-beat.json respectively. Run
exactly one beat replica — it's the scheduler, and duplicates would
double-publish.
Verifying migrations
After a deploy, check what's applied on the target service (or locally):
python manage.py showmigrations db | tail
Unapplied migrations show as [ ]; applied ones as [X]. db is the app that
holds WriteMars's models.
To confirm models and migrations haven't drifted (also a CI check):
python manage.py makemigrations --check
Writing migrations
Keep migrations additive whenever possible — add columns/tables rather than
renaming or dropping in the same release that ships the code depending on them.
Because migrate runs before the new code cuts over (and briefly alongside
the old code), a destructive change applied in the same deploy can break the
still-running old revision. When a drop is unavoidable, split it across two
deploys: stop using the column first, drop it later.