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:

  1. Collecting: finding files from STATICFILES_DIRS and each app's static/ directory and copying them to STATIC_ROOT.
  2. Post-processing: with ManifestStaticFilesStorage (or WhiteNoise's CompressedManifestStaticFilesStorage), 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.

What collectstatic doesSourcesapps, STATICFILES_DIRSCollectcopy to STATIC_ROOTPost-processhash and rewriteManifeststaticfiles.jsonMost failures happen in post-processing, where every referenced file must exist.

Quick answer

  1. Read the last lines of the error: the exception type and the file it names.
  2. ImproperlyConfigured ... STATIC_ROOT: set STATIC_ROOT to a filesystem path.
  3. 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.
  4. Errors mentioning .map files: add the missing source maps or remove sourceMappingURL comments from vendored files.
  5. PermissionError: the build user cannot write to STATIC_ROOT.
  6. KeyError or ImproperlyConfigured about other settings: settings require runtime environment variables at build time; provide build-safe values.
  7. Re-run collectstatic locally 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
collectstatic errors, decodedErrorUsual causeImproperlyConfigured: STATIC_ROOTSTATIC_ROOT not set in these settingsThe file … could not be foundCSS or JS references a missing filePermissionErrorSTATIC_ROOT not writable by this userKeyError: DATABASE_URLsettings need runtime-onlyvalues at buildReproduce locally with the same settings module firstdjango-deployment.com
Most of these only appear with manifest storage, which is why local dev misses them.

--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...>.
reading the missing-file errorValueError: The file'fonts/icons.woff2'could not be found with<django.contrib.staticfiles.storage.ManifestStaticFilesStorage…>.Post-processing'vendor/icons/css/icons.css'failed!1231The missing target, relative to the CSS file.2Only manifest storage rewrites and checks references.3The file containing the broken url().Open the referencing file and search for the target namedjango-deployment.com
Commented-out url() lines in CSS are still processed.

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:

  1. Add the missing file. Vendored CSS often expects fonts or images you did not copy.
  2. Fix the path. Relative url() references resolve relative to the CSS file's location; case must match exactly on Linux.
  3. 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 .map file alongside the minified file
  • remove the sourceMappingURL comment from the vendored file
  • install the library through a package manager and include its complete dist directory

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 dist directory, 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=.*\*/##' {} +
third-party files you cannot fix by handEdit the vendor file✕lost on the next package update✕nobody remembers the patch✕impossible inside site-packagesConfigure storage✓manifest_strict = False subclass✓narrow the patterns for that app✓or ship the missing fileRelax the check narrowly; do not turn off hashing for everythingdjango-deployment.com
Record the reason in a comment on the storage subclass.

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'
what the build step really needsNeeded at build?STATIC_ROOT, STORAGES✓yesINSTALLED_APPS✓yesSECRET_KEY!any value worksDATABASE_URL✕no: do not require itSettings that demand runtime secrets break image buildsdjango-deployment.com
A dummy SECRET_KEY for the build step keeps real secrets out of the image.

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:

  • collectstatic completes without errors or unexpected duplicate warnings
  • staticfiles.json exists 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 (default True) controls runtime lookups of names missing from the manifest.
  • manifest_storage (Django 4.2+) lets you store staticfiles.json in a different storage than the collected files, for example locally while files go to S3.
  • support_js_module_import_aggregation (Django 4.2+, default False) enables rewriting of JavaScript module import and export statements. 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_ROOT inside STATICFILES_DIRS.
  • Committing STATIC_ROOT output to Git and collecting on top of stale files.
  • Running collectstatic --clear on a live server during traffic.
  • Disabling manifest storage entirely to "fix" the build, which reintroduces stale caching problems.

Next steps

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.

Keep going