skip to content

On a re-run, how does Django's collectstatic decide which files to copy, and when do you need --clear, --link or --ignore?

level: seniorimportance: nice to knowfreq 26%

answer

  1. existing target, compare timestamps
  2. deleted sources linger
  3. wipe the destination first
  4. symlinks only on local disk
  5. default ignore patterns

basics

~20 s

Django's collectstatic copies each first-found file but skips an existing target at least as new as its source. It never deletes files whose source vanished; --clear wipes the destination first, --link symlinks instead of copying, --ignore skips glob patterns.

solid answer

~50 s

`collectstatic` walks the finders, keeps the first file per destination path, and for each one checks the target in the static storage. If the target exists and its modified time, compared to the second, is not older than the source's, the file is counted as **unmodified** and skipped; otherwise it is deleted and copied again. Storages that cannot report times are always recopied. Files removed from the sources are **never** removed from `STATIC_ROOT`, so stale assets linger until you run `--clear`, which deletes the existing files through the storage before collecting. `--link` creates symlinks instead of copies and fails with "Can't symlink to a remote destination." on non-local storage. `--ignore PATTERN` adds glob patterns on top of the defaults `CVS`, `.*` and `*~` (`--no-default-ignore` drops those). `--noinput` skips the "yes" confirmation prompt, and `--dry-run` shows what would happen.

code

bash · 3 lines
bash
# Preview, then collect a clean copy without source-only files
python manage.py collectstatic --dry-run -v 2 --ignore "*.scss"
python manage.py collectstatic --noinput --clear --ignore "*.scss"

go deeper

for a junior

Know that collectstatic copies files into STATIC_ROOT and that --noinput is used in scripts.

for a middle

Explain the timestamp-based skip, the default ignore patterns, and what --clear, --link and --dry-run each change.

for a senior

Diagnose stale or lingering assets from how collectstatic only adds and replaces, and choose between --clear and a fresh output directory per build.

for a principal

Decide how static output is produced and retired across environments so a removed asset is guaranteed to disappear.

## What one run does `manage.py collectstatic` gathers every file the finders can see into the storage named by `STORAGES["staticfiles"]`, by default `StaticFilesStorage` writing to `STATIC_ROOT`. In outline: 1. Unless `--noinput` is given, and when the destination already holds files or is not local, it asks for confirmation and requires the literal answer `yes`, warning that files will be overwritten, or with `--clear` that **all files** in the location will be deleted. 2. With `--clear`, it deletes the existing contents through the storage. 3. It asks each finder to **list** its files, skipping names that match the ignore patterns. 4. For each destination path, the **first** file found wins; later ones are skipped as conflicts. 5. For each winner it decides whether to copy, or with `--link` symlink, the file. 6. If the storage defines `post_process()`, it runs after collection, unless `--no-post-process` is passed. 7. It prints a summary: copied, unmodified, deleted and skipped counts. ## The copy-or-skip decision For every file, `delete_file()` looks at the existing target: - **No target**: copy. - **Target exists, times available**: compare the target's modified time with the source's, both truncated to whole seconds. If the target is the same age or newer, the file is **unmodified** and skipped. - **Switching modes**: on local storage, if the target is a symlink and this run copies, or the reverse, the file is replaced even when unchanged, so link and copy never mix. - **Storage without modified times**: the check is skipped and the file is deleted and copied every time. This makes re-runs fast, but it trusts timestamps. If a deploy process preserves old modification times on changed source files, a stale copy in `STATIC_ROOT` can be considered up to date. `--clear` sidesteps the comparison entirely. ## Why stale files linger `collectstatic` only **adds and replaces**. It has no notion of a file that used to exist: - Delete `exhibits/static/exhibits/old-map.js` from the source, re-run, and `STATIC_ROOT/exhibits/old-map.js` is still there, still served. - Rename an asset, and both names are now served. - Remove an app from `INSTALLED_APPS`, and its collected files stay. Mostly harmless, but it bloats the output and can keep serving an asset you meant to retire, including one you removed for a security reason. When the output must match the sources exactly, use `--clear`, or collect into a fresh directory on every build. ## Options at a glance | Option | Effect | |---|---| | `--noinput`, `--no-input` | no confirmation prompt, for scripts | | `--clear` | delete existing files in the storage before collecting | | `--link` | symlink instead of copy; local storage only | | `--ignore PATTERN` | extra glob pattern to skip; repeatable | | `--no-default-ignore` | stop ignoring `CVS`, `.*` and `*~` | | `--dry-run` | do everything except modify the filesystem | | `--no-post-process` | skip the storage's post-processing step | The default ignore patterns come from the staticfiles app config; a project can change them for good by subclassing `StaticFilesConfig` and overriding `ignore_patterns`, then listing that config in `INSTALLED_APPS` in place of `django.contrib.staticfiles`. ## When to use which - **`--clear`** when assets were removed or renamed, when timestamps are unreliable, or when a fresh build directory is not an option. - **`--link`** on a single machine where `STATIC_ROOT` and the sources share a filesystem and disk space or copy time matters; not for remote storages or containers where the source tree is absent at runtime. - **`--ignore`** to keep source-only material out of production, for example `--ignore "*.scss"` or `--ignore "src"`. - **`--dry-run`** with `-v 2` to preview exactly what a change to finders or ignore patterns will do.

  • Why can --link not be used with a remote storage?
    A symlink is a local filesystem object pointing at the source file; an object store has no equivalent. `collectstatic` checks whether the storage is local and raises "Can't symlink to a remote destination." when it is not. Symlinks also assume the source tree exists wherever `STATIC_ROOT` is served from.
  • Does --clear make collectstatic slower on every deploy?
    Yes, since nothing can be skipped as unmodified: every file is copied again, and a storage with post-processing redoes that work. Collecting into a fresh, empty directory per build gives the same exactness, and many pipelines do that instead of clearing a long-lived `STATIC_ROOT`.

saying these in an interview costs you the question

  • collectstatic mirrors the sources, deleting files that no longer exist
  • collectstatic compares file contents to decide what to copy
  • --link works with any storage, including remote object stores
  • Dotfiles are collected unless you pass --ignore
  • --noinput changes which files are collected