In a containerised Django deployment, why does collectstatic run when the image is built while migrate runs once per release before new code takes traffic?
answer
- what each command writes to
- files in the image vs shared database
- STATIC_ROOT and the hashed manifest
- schema must lead the code
basics
~20 scollectstatic reads files already in the code and writes them to STATIC_ROOT, so its output belongs in the immutable image. migrate alters the shared database, so it runs once per release, before new-code containers serve requests.
solid answer
~40 s`collectstatic` gathers every app's `static/` directory and `STATICFILES_DIRS` into `STATIC_ROOT`; when `STORAGES["staticfiles"]` uses `ManifestStaticFilesStorage` it also writes hashed file names and `staticfiles.json`. It needs no database and depends only on what is in the image, so running it once in the image build gives every container the same assets and the same manifest. `migrate` is the opposite: its target is the one database that old and new containers share, so it runs once per release as its own step rather than in every container's start command (Django's `migrate` takes no lock of its own), and it must finish before code that expects the new schema takes traffic, or queries naming new columns fail. The order is: build the image with `collectstatic`, run `migrate` once, then start the new containers.
code
bash · 7 lines# inside the image build: output depends only on the code
python manage.py collectstatic --noinput
# once per release, from the new image, against the production database
python manage.py migrate --noinput
# only after migrate succeeds: start or switch to the new app-server containersgo deeper
Know that collectstatic copies static files into STATIC_ROOT and migrate applies schema changes to the database, and that production runs both as deliberate steps, not by hand.
Explain why collectstatic belongs in the image build and migrate in a single release step, and what ManifestStaticFilesStorage writes and reads.
Lay out the release order, name the failures when a step is misplaced, and explain why migrate must not run in every container's start command.
Frame the build-versus-release split as a policy: immutable images, one schema change per release, and a pipeline that fails loudly at the cheapest point.
## Two commands, two very different targets A Django release usually involves two management commands that sit next to each other on a deploy checklist but touch completely different things. **`collectstatic`** (from `django.contrib.staticfiles`) reads files that ship with the code and writes them to a directory. **`migrate`** reads the migration files that ship with the code and writes to the **database**, the one piece of state that every running container, old and new, shares. | Command | Reads | Writes | Needs the database? | Natural home | |---|---|---|---|---| | `collectstatic` | each app's `static/` directory and `STATICFILES_DIRS` | `STATIC_ROOT`, plus `staticfiles.json` with `ManifestStaticFilesStorage` | No | the image build | | `migrate` | each app's `migrations/` package and the `django_migrations` table | the schema and the `django_migrations` table | Yes | once per release, before traffic | Where a step runs follows from what it writes. ## collectstatic belongs to the build `collectstatic` copies every static file its finders locate into `STATIC_ROOT`. When `STORAGES["staticfiles"]` points at `ManifestStaticFilesStorage`, it then **post-processes** the files: it renames each one with a content hash (`site.css` becomes something like `site.3f2a9c1b.css`), rewrites the references inside CSS, and writes a manifest, `staticfiles.json`, that maps original names to hashed names. - The output depends **only on the code and settings** in the image, so building it once guarantees that every container started from that image serves identical assets and reads an identical manifest. - It needs **no database connection** and runs only the system checks tagged `staticfiles`, so it fits a build step that has no production access. - Running it at container start instead repeats identical work on every boot, slows scaling, and needs a writable `STATIC_ROOT` at runtime. - A broken reference, such as a stylesheet pointing at an image that does not exist, fails post-processing and therefore **fails the build**, which is the cheapest place to find it. ## migrate belongs to the release `migrate` compares the migration graph in the code with the rows in `django_migrations` and applies whatever is missing. Its target is shared, so the reasoning flips: - It must run **once per release**, not once per container. Django's `migrate` takes no lock of its own, so several containers each running it on start can race to apply the same plan. - It must **finish before code that expects the new schema takes traffic**. The new code's queries name columns and tables that only exist after the migration; until then they fail with database errors. - It needs **production database credentials**, which a build step should not hold. An image built with them, or one whose build migrated a particular database, stops being environment-independent. Whether that single run is a separate release job, a pre-deploy hook or an init task is a platform choice. The Django-specific facts are the two above: one run, and before the new code serves. ## The release order 1. **Build** the image from the commit, running `collectstatic --noinput` inside the build so the hashed files and the manifest are part of the image. 2. **Run `migrate --noinput` once** against the production database, using the new image so the migration files match the code about to ship. 3. **Start the new containers** and let the platform's health checks decide when they receive traffic. 4. **Retire the old containers**, which for a short time run against the already-migrated schema. Step 4 is why migrations for a rolling release are written so the previous release keeps working against the new schema; that discipline is a subject of its own. ## What goes wrong when a step is misplaced - **`migrate` in the image build**: the build either cannot reach the database or migrates it before the release is approved, and the image is now tied to one environment. - **`migrate` in every web container's start command**: concurrent attempts on the same plan, and a failing migration shows up as containers that crash on boot rather than as one failed release step. - **New code before `migrate`**: requests that touch new fields fail until the migration lands. - **No `collectstatic` in the image**: with `DEBUG = False` and `ManifestStaticFilesStorage`, the `{% static %}` tag raises `ValueError: Missing staticfiles manifest entry` because `manifest_strict` defaults to `True`, so pages that reference static files return a 500. With `DEBUG = True` the storage returns unhashed URLs, which is why this bug hides in local development. - **`collectstatic --no-post-process` to get a failing build through**: no hashed files and no manifest are written, so the same `ValueError` moves from the build log to production requests.
- What happens if a Django image uses ManifestStaticFilesStorage but collectstatic never ran for it?With `DEBUG = False`, every `{% static %}` lookup goes through `staticfiles.json`. There is no manifest, so the storage finds no entry and, because `manifest_strict` defaults to `True`, raises `ValueError: Missing staticfiles manifest entry for '...'`; pages that reference static files return a 500. With `DEBUG = True` the storage returns unhashed URLs, which is why the bug does not show up locally.
- Why not run collectstatic when each container starts instead of at build time?It repeats identical work on every boot and slows scaling, it needs a writable `STATIC_ROOT` at runtime, and if several versions share one destination they overwrite each other's files and manifest. `ManifestStaticFilesStorage` also reads `staticfiles.json` once, when the storage is first created in a process, so a manifest rewritten after workers started is not picked up until they restart.
collectstatic is printing the menus before the restaurant opens: every copy is identical and made once from the recipes. migrate is rearranging the one shared kitchen: you do it once, before the new menu goes out, not once per waiter.
saying these in an interview costs you the question
- Run migrate in the image build so the database is ready with the image.
- collectstatic needs production database credentials to run.
- Every web container can run migrate on start because Django locks the migration table.
- It does not matter whether migrate runs before or after new containers take traffic.
- Hashed static URLs working with DEBUG = True proves the production manifest is fine.