skip to content

How do you serve a Django app's static files with WhiteNoise when it runs in a single container with no separate web server?

level: juniorimportance: must knowfreq 55%

answer

  1. Django will not do it alone
  2. collect into STATIC_ROOT first
  3. middleware plus a storage backend
  4. files must exist before startup
  5. runserver_nostatic for dev parity

basics

~10 s

Install WhiteNoise, add WhiteNoiseMiddleware right after SecurityMiddleware, set STATIC_ROOT and STORAGES['staticfiles'] to CompressedManifestStaticFilesStorage, and run collectstatic while building the image so the files exist before the app starts.

solid answer

~40 s

With `DEBUG = False` Django itself does not serve static files; `runserver`'s static handling is a development convenience. In a container with only the application server, WhiteNoise fills that gap from inside the Django process. You add `whitenoise.middleware.WhiteNoiseMiddleware` directly after `SecurityMiddleware`, set `STATIC_ROOT`, and set `STORAGES['staticfiles']['BACKEND']` to `whitenoise.storage.CompressedManifestStaticFilesStorage` for hashed, compressed files. `collectstatic` must run while the image is built, because WhiteNoise indexes `STATIC_ROOT` once when the process starts. Templates keep using `{% static %}` so they emit the hashed names. For development parity, `whitenoise.runserver_nostatic` at the top of `INSTALLED_APPS` makes `runserver` hand static files to WhiteNoise too. User uploads are a separate problem: WhiteNoise's docs say not to serve media with it.

code

python · 5 lines
python
INSTALLED_APPS = [
    'whitenoise.runserver_nostatic',
    'django.contrib.staticfiles',
    # ...
]

go deeper

for a junior

Recall the four settings pieces: STATIC_ROOT, the middleware after SecurityMiddleware, the WhiteNoise storage backend in STORAGES, and collectstatic before start-up.

for a middle

Explain why WhiteNoise needs collected files at process start and what runserver_nostatic changes for local development.

for a senior

Design the image build so every replica carries the same collected assets, and keep uploads out of WhiteNoise entirely.

for a principal

Judge when app-served static files stop being enough and a CDN or separate storage becomes worth its operational cost.

## The gap WhiteNoise fills Django's `staticfiles` app finds and collects assets, but it deliberately does not serve them in production. The development server's static handler only runs when `DEBUG` is `True` (or with `runserver --insecure`), and Django's docs treat that path as unsuitable for production. The classic answer is a separate web server in front of Django that serves `STATIC_ROOT` directly. When the deployment is **one container running only the application server**, there is no such server, and that is the scenario **WhiteNoise** is designed for: the Django process serves its own collected assets, with sensible caching and compression. ## The setup, step by step 1. **Install the package** (`pip install whitenoise`, or `whitenoise[brotli]` to also produce Brotli files). 2. **Set `STATIC_ROOT`**, the directory `collectstatic` writes into, for example `BASE_DIR / 'staticfiles'`. 3. **Add the middleware** directly after `SecurityMiddleware` in `MIDDLEWARE`. 4. **Choose the storage backend** under the `staticfiles` alias of `STORAGES`: `whitenoise.storage.CompressedManifestStaticFilesStorage` adds content-hashed names and pre-compressed copies. 5. **Run `collectstatic --noinput` during the image build**, so `STATIC_ROOT` is populated inside the image. 6. **Reference assets with `{% static %}`**, never with hand-written `/static/...` paths, so templates pick up the hashed names. ```python STATIC_URL = 'static/' STATIC_ROOT = BASE_DIR / 'staticfiles' STORAGES = { 'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'}, 'staticfiles': { 'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage', }, } ``` ## Why the build order matters In production WhiteNoise reads `STATIC_ROOT` **once**, in the middleware's constructor, and serves from that in-memory index. Two consequences follow: - If `collectstatic` runs in a start-up script after the server has already booted, the processes that booted first serve 404s for every asset until restarted. - If `STATIC_ROOT` is a volume mounted at run time and populated later, the same thing happens. Baking the collected files into the image avoids both, and it also means every replica of the container carries identical assets. ## Development parity By default `runserver` handles static files itself when `DEBUG` is on, so WhiteNoise's headers and compression never appear locally. Adding `whitenoise.runserver_nostatic` **above** `django.contrib.staticfiles` in `INSTALLED_APPS` changes `runserver`'s default to the equivalent of `--nostatic`, and WhiteNoise serves files in development too. In `DEBUG` mode WhiteNoise also defaults `WHITENOISE_USE_FINDERS` and `WHITENOISE_AUTOREFRESH` to `True`, so it finds files in app directories and picks up edits without `collectstatic`. ## What WhiteNoise is not for | Concern | Guidance | |---|---| | User uploads (`MEDIA_ROOT`) | Not suitable: files added after start-up are unseen, and serving uploads from the app's own domain is a security risk | | Remote storage backends | WhiteNoise only serves files stored on the local filesystem in `STATIC_ROOT` | | Very high asset traffic | Put a CDN in front; WhiteNoise's cache headers let it cache hashed files for a long time | ## Common mistakes - Forgetting `STATIC_ROOT`, so `collectstatic` has nowhere to write. - Configuring the storage through `STATICFILES_STORAGE` from an old guide; that setting was removed in Django 5.1. - Leaving a `node_modules` tree inside `STATIC_ROOT`, which slows start-up because every file is indexed.

  • Why do WhiteNoise's docs rule it out for user-uploaded MEDIA files?
    It indexes files once at start-up, so uploads made afterwards are not seen. Serving user-controlled files from the application's own domain is also a security risk, and local disk makes scaling across machines harder. Uploads belong on a dedicated storage service served from elsewhere.
  • Can WhiteNoise serve files without running collectstatic at all?
    Yes, with `WHITENOISE_USE_FINDERS = True` it finds files in their original directories through Django's finders, which is the default under `DEBUG`. Its docs allow this in production only if you give up the hashing and compression that the storage backends provide during `collectstatic`.

saying these in an interview costs you the question

  • Django serves static files itself in production once DEBUG is False.
  • Running collectstatic after the server starts is fine because WhiteNoise rescans.
  • WhiteNoise is a good way to serve user-uploaded media from the same container.
  • WhiteNoise works with any storage backend, including remote object storage.
  • Templates can hardcode /static/ paths since WhiteNoise rewrites them.