Fix collectstatic Errors in Django Deployments
Fix Django collectstatic failures: missing STATIC_ROOT, broken CSS references, source map errors, duplicate paths, permissions, and build-time settings.
Problem statement
python manage.py collectstatic fails during a deploy or Docker build, and the release stops. On platforms such as Heroku or Azure App Service, the build fails before your app ever starts. The error messages are often long and point at a CSS file you did not write, such as a vendored library stylesheet.
collectstatic does two separate jobs, and failures come from either:
- Collecting: finding files from
STATICFILES_DIRSand each app'sstatic/directory and copying them toSTATIC_ROOT. - Post-processing: with
ManifestStaticFilesStorage(or WhiteNoise'sCompressedManifestStaticFilesStorage), hashing filenames and rewriting references inside CSS and JavaScript files to the hashed names.
Most confusing errors come from post-processing, which parses CSS url() references, @import rules, and source map comments, and fails when a referenced file does not exist.
Quick answer
- Read the last lines of the error: the exception type and the file it names.
ImproperlyConfigured ... STATIC_ROOT: setSTATIC_ROOTto a filesystem path.ValueError: The file '...' could not be found with <...ManifestStaticFilesStorage...>: a CSS or JS file references a missing file; fix the reference, add the file, or exclude the vendored asset from post-processing.- Errors mentioning
.mapfiles: add the missing source maps or removesourceMappingURLcomments from vendored files. PermissionError: the build user cannot write toSTATIC_ROOT.KeyErrororImproperlyConfiguredabout other settings: settings require runtime environment variables at build time; provide build-safe values.- Re-run
collectstaticlocally with production settings until it passes, then deploy.
Step-by-step solution
1. Reproduce locally with production settings
Run the same command the build runs, with the same settings module:
DJANGO_SETTINGS_MODULE=config.settings \
DJANGO_DEBUG=0 \
DJANGO_SECRET_KEY=build-only \
python manage.py collectstatic --noinput --verbosity 2
--verbosity 2 prints each file as it is processed, so the last file before the error is usually the one that references something missing.
2. STATIC_ROOT is not set
django.core.exceptions.ImproperlyConfigured: You're using the staticfiles app without
having set the STATIC_ROOT setting to a filesystem path.
Set it:
STATIC_ROOT = BASE_DIR / "staticfiles"
A related system check error:
(staticfiles.E002) The STATICFILES_DIRS setting should not contain the STATIC_ROOT setting.
STATIC_ROOT is an output directory. It must not also be listed as an input in STATICFILES_DIRS. Use a separate source directory such as BASE_DIR / "static" for your own assets.
3. A referenced file cannot be found
The most common manifest error:
ValueError: The file 'fonts/icons.woff2' could not be found with
<django.contrib.staticfiles.storage.ManifestStaticFilesStorage object at 0x7f...>.
Django tells you the missing target, and with verbosity it shows which file referenced it:
Post-processing 'vendor/icons/css/icons.css' failed!
Open that CSS file and search for the reference:
grep -n "icons.woff2" staticfiles_source/vendor/icons/css/icons.css
Then fix it, in order of preference:
- Add the missing file. Vendored CSS often expects fonts or images you did not copy.
- Fix the path. Relative
url()references resolve relative to the CSS file's location; case must match exactly on Linux. - Remove the unused rule if the asset is not needed.
References that post-processing ignores: absolute URLs (https://...), data URIs (data:), and fragment-only references. Paths starting with / are resolved relative to STATIC_ROOT-style URLs and can fail too.
4. Source map references
Minified vendor files often end with a comment like:
//# sourceMappingURL=library.min.js.map
Manifest storage also rewrites these references in JavaScript and CSS, so a missing .map file fails post-processing:
ValueError: The file 'vendor/library.min.js.map' could not be found with <...>.
Options:
- copy the
.mapfile alongside the minified file - remove the
sourceMappingURLcomment from the vendored file - install the library through a package manager and include its complete
distdirectory
5. Third-party files you cannot edit by hand
Vendored libraries copied into static/ are the usual source of broken references, and editing them by hand is lost on the next upgrade. Make the fix repeatable instead:
- Install assets with a package manager (npm, for example) and copy the library's complete
distdirectory, including fonts, images, and source maps, into your static source directory during the build. - Bundle frontend assets with a build tool such as Vite or esbuild, and collect only the bundled output. Bundlers resolve imports and copy referenced assets, so the collected CSS no longer contains fragile relative references.
- Strip source map comments in a build step when you do not ship maps:
find static/vendor -name "*.min.js" -exec sed -i '/^\/\/# sourceMappingURL=/d' {} +
find static/vendor -name "*.min.css" -exec sed -i 's#/\*\# sourceMappingURL=.*\*/##' {} +
Avoid subclassing the storage to skip post-processing for some files: skipped files are missing from staticfiles.json, so {% static %} lookups for them fail at runtime.
manifest_strict = False is sometimes suggested for this problem, but it only changes what happens when templates ask for a file that is missing from the manifest at runtime; it does not stop post-processing failures during collectstatic.
6. Circular or deeply nested imports
RuntimeError: Max post-process passes exceeded.
Post-processing repeats until references stabilize. Two CSS files that import each other, or chains of @import longer than the pass limit, never stabilize. Remove the cycle, or bundle the CSS with a frontend build tool so the collected file no longer contains @import chains.
7. Duplicate destination paths
Found another file with the destination path 'css/app.css'. It will be ignored since
only the first encountered file is collected.
This is a warning, not an error, but it matters: only the first file found (by STATICFILES_DIRS order, then INSTALLED_APPS order) is collected. If the wrong one wins, the site uses unexpected CSS. Namespace your app static files (myapp/static/myapp/css/app.css) to avoid collisions.
8. Permission errors
PermissionError: [Errno 13] Permission denied: '/srv/myapp/staticfiles/admin'
The user running collectstatic cannot write to STATIC_ROOT. Fix ownership for the deploy user, not with world-writable modes:
sudo chown -R deploy:deploy /srv/myapp/staticfiles
In Docker, run collectstatic before switching to a non-root user, or create STATIC_ROOT owned by that user.
9. Settings that fail at build time
In container builds and PaaS builds, runtime secrets are often absent:
KeyError: 'DATABASE_URL'
collectstatic imports settings but does not need a database, secret key, or API credentials. Make build-time imports succeed:
- provide throwaway values for required variables during the build step only, for example
DJANGO_SECRET_KEY=build-only - avoid connecting to services at import time (in
settings.py,AppConfig.ready(), or module-level code) - read optional integrations lazily
10. Verify
python manage.py collectstatic --noinput --clear
python manage.py findstatic admin/css/base.css
test -f staticfiles/staticfiles.json && python -c "import json; print(len(json.load(open('staticfiles/staticfiles.json'))['paths']))"
--clear removes existing files first, which is safe in a build directory but not on a live server where running processes still reference old hashed files.
Verification check:
collectstaticcompletes without errors or unexpected duplicate warningsstaticfiles.jsonexists and lists the expected number of paths- pages render with hashed static URLs and assets return
200
Explanation
Manifest storage makes static files safe to cache forever by embedding a content hash in each filename. To keep references valid, it must rewrite every CSS and JavaScript reference to the hashed names, which means every referenced file must exist at collection time. Development never exercises this, because runserver serves unhashed source files. Errors therefore appear first in the build pipeline, pointing at references that were silently broken all along.
Edge cases / notes
WhiteNoise-specific errors
WhiteNoise's CompressedManifestStaticFilesStorage raises a MissingFileError with similar meaning. The same fixes apply.
Very slow collectstatic
Compression and hashing thousands of files takes time. Remove unused apps and vendor directories, and run collectstatic once per build rather than on every container start.
Storage backends that upload to S3
With S3ManifestStaticStorage, network errors and permissions also cause failures. Test credentials separately and collect from CI with retry.
Catch broken references before they reach the build
Run collectstatic in CI on every pull request, with the same storage backend as production:
# GitHub Actions step
- name: Collect static files with manifest storage
env:
DJANGO_SETTINGS_MODULE: config.settings
DJANGO_SECRET_KEY: ci-only-not-secret
DJANGO_DEBUG: "0"
run: |
python manage.py collectstatic --noinput --verbosity 1
python manage.py check --deploy --fail-level ERROR
A pull request that adds a vendored stylesheet with a missing font then fails in CI, with the offending file named in the log, instead of failing a production deploy.
Finding every broken reference at once
Post-processing stops at the first missing file, which makes fixing many broken references a slow loop. A small script lists url() references in collected-source CSS whose relative targets do not exist:
# scripts/find_missing_css_refs.py
import re
from pathlib import Path
URL_RE = re.compile(r"url\(\s*['\"]?([^'\")]+)['\"]?\s*\)")
for css in Path("static").rglob("*.css"):
text = css.read_text(errors="ignore")
for ref in URL_RE.findall(text):
if ref.startswith(("data:", "http:", "https:", "//", "#", "/")):
continue
target = (css.parent / ref.split("?")[0].split("#")[0]).resolve()
if not target.exists():
print(f"{css}: missing {ref}")
Adjust the root directory to your STATICFILES_DIRS. The script skips absolute paths and URLs, which post-processing treats differently, and ignores query strings and fragments often used for cache busting or SVG sprites.
Manifest storage options worth knowing
manifest_strict(defaultTrue) controls runtime lookups of names missing from the manifest.manifest_storage(Django 4.2+) lets you storestaticfiles.jsonin a different storage than the collected files, for example locally while files go to S3.support_js_module_import_aggregation(Django 4.2+, defaultFalse) enables rewriting of JavaScript moduleimportandexportstatements. Enable it only if you serve ES modules through Django's static storage, because it can surface new missing-file errors in module graphs.
Common mistakes
- Listing
STATIC_ROOTinsideSTATICFILES_DIRS. - Committing
STATIC_ROOToutput to Git and collecting on top of stale files. - Running
collectstatic --clearon a live server during traffic. - Disabling manifest storage entirely to "fix" the build, which reintroduces stale caching problems.
Next steps
- If files are collected but not served, see Django Static Files Not Loading in Production.
- Understand hashing and caching in Long-Lived Caching for Django Static Files.
- Serve collected files with How to Serve Django Static Files with WhiteNoise.
FAQ
Why does collectstatic fail with "could not be found with ManifestStaticFilesStorage"?
A CSS or JavaScript file references another file, such as a font, image, or source map, that does not exist in your static sources. Add the file, fix the path, or remove the reference.
Does manifest_strict = False fix collectstatic errors?
No. It only affects template lookups for files missing from the manifest at runtime. Post-processing during collectstatic still fails on broken references.
Why does collectstatic need my database URL during a Docker build?
It does not need a database, but importing settings may require the variable. Provide build-only placeholder values and avoid connecting to services at import time.
Is the "Found another file with the destination path" warning a problem?
It can be. Only the first matching file is collected, so another app's file may override yours. Namespace static files under a directory named after the app.