Fix "You're using the staticfiles app without having set STATIC_ROOT"
Fix the Django ImproperlyConfigured error You’re using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path: set STATIC_ROOT correctly, keep it separate from STATICFILES_DIRS, and run collectstatic in deploys.
Problem statement
Running collectstatic during a deploy, on Heroku, Render, or in a Docker build fails with:
django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without having
set the STATIC_ROOT setting to a filesystem path.
collectstatic gathers static files from every app and from STATICFILES_DIRS into one directory that a web server or WhiteNoise can serve. That destination is STATIC_ROOT. In development you never need it, because runserver serves files directly from each app, so many projects never set it until the first production deploy.
- Add
STATIC_ROOT = BASE_DIR / "staticfiles"to your production (or base) settings. - Keep
STATIC_URL = "/static/". - Do not put
STATIC_ROOTinsideSTATICFILES_DIRS, and do not point it at your sourcestatic/folder. - Run
python manage.py collectstatic --noinputin the build or deploy step. - Serve
STATIC_ROOTwith Nginx (alias) or WhiteNoise.
Step-by-step solution
1. Set STATIC_ROOT
# settings/base.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles" # collectstatic writes here
STATICFILES_DIRS = [BASE_DIR / "static"] # your source assets, optional
Check which settings module the failing command loads; the error often appears because STATIC_ROOT is defined only in a local settings file that production does not import.
2. Keep source and destination separate
STATICFILES_DIRS lists source directories. STATIC_ROOT is the output. If they overlap, Django raises a different error ("The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting"), and collectstatic would copy files into the folder it reads from. Use distinct names such as static/ for sources and staticfiles/ for output, and add the output to .gitignore.
3. Run collectstatic where it belongs
- VPS: in the deploy script after installing dependencies:
python manage.py collectstatic --noinput. - Docker: in the Dockerfile so the image contains the files:
RUN python manage.py collectstatic --noinput(settings must import without production secrets; provide a dummySECRET_KEYfor the build step if needed). - Heroku: the Python buildpack runs it automatically when it detects Django; set
DISABLE_COLLECTSTATIC=1only if you handle it yourself. - Render, Railway: add it to the build command.
4. Serve STATIC_ROOT
With WhiteNoise:
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
With Nginx:
location /static/ {
alias /srv/app/staticfiles/;
}
The Nginx path must match STATIC_ROOT exactly, with trailing slashes on both sides.
5. Build-time settings in Docker
A common Docker failure is that settings require environment variables (database URL, secret key) that do not exist during docker build, so collectstatic fails for an unrelated reason. Keep static settings importable without secrets:
RUN SECRET_KEY=build-only DJANGO_SETTINGS_MODULE=config.settings.production \
python manage.py collectstatic --noinput
Verification check:
python manage.py collectstatic --noinput
ls staticfiles/admin/css/base.css
curl -sI https://example.com/static/admin/css/base.css | head -1
Explanation
The staticfiles app separates where static files live during development (inside each app and in STATICFILES_DIRS) from where they are served in production (one directory or a storage backend). In development, the runserver command finds files with the staticfiles finders on every request. In production that would be slow and requires DEBUG, so collectstatic copies everything once into STATIC_ROOT. The setting is only required when you run collectstatic, which is why the error appears at the first deploy rather than during development.
Edge cases / notes
- Remote storage. If
STORAGES["staticfiles"]points at S3,STATIC_ROOTis not used for serving, but some tools still expect it; set it anyway. - Permissions. The deploy user must be able to write
STATIC_ROOT, and Nginx must be able to read it. - Admin CSS missing in production is usually this same pipeline not running or not being served.
- Monorepos. Use an absolute path derived from
BASE_DIR, never a relative path that depends on the working directory.
Next steps
- For other collectstatic failures, see Fix collectstatic Errors in Django.
- Serve files without Nginx using Serve Static Files With WhiteNoise in Django.
FAQ
What should STATIC_ROOT be set to?
An absolute path to an output directory, commonly BASE_DIR / "staticfiles", separate from your source static/ folder.
Do I need STATIC_ROOT in development?
No. runserver serves files from app directories. It is needed for collectstatic.
Can STATIC_ROOT be inside STATICFILES_DIRS?
No. Django rejects it, and it would copy files into their own source.
Where should collectstatic run?
In the build or deploy step, so production starts with files already collected.