Skip to main content

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:

  1. Builds the image.
  2. Runs python manage.py migrate --noinput as the preDeployCommand — a separate pre-cutover container, so schema changes land before any new code serves traffic (backend/railway.json).
  3. Starts the web service via /app/bin/docker-entrypoint-api.sh, which execs gunicorn serving writemars.config.wsgi. (The entrypoint can also migrate on start, but on Railway that's disabled via MIGRATE_ON_START=false because 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.