Course outline

Django / Correctness and production evidence / Lesson 16

Static assets, media, and deployment

Separate authored assets from untrusted uploads and coordinate build artifacts, storage, and the production request boundary.

About 4 min readContent reviewed
On this page

Lesson goals

  • Static and media files have different trust
  • Verify discovery with an isolated temporary asset
  • Review the complete production boundary

Static and media files have different trust

Static assets are application-controlled files such as stylesheets and scripts. Media uploads are user-controlled data with different validation, retention, and serving requirements. Do not place uploads in an executable static path merely because both are files. Define size limits, content handling, authorization, and storage ownership for media. A public URL is not a permission system for private files; access may require an authorized download view or another reviewed delivery mechanism. Keep original filenames and content type claims untrusted, and avoid interpreting uploaded files as templates or application code. The filesystem layout should reflect these different responsibilities.

Discovery, collection, and serving are separate stages

Static-file finders locate authored assets under configured directories and apps. A build or deployment step can collect and process them into a publication artifact, and a web server, storage service, or reviewed middleware serves that artifact. A successful development asset request does not establish production serving. STATIC_URL identifies a URL prefix, while STATIC_ROOT is a collection destination and STATICFILES_DIRS identifies source directories under the relevant configuration. Mixing those roles can create confusing paths or overwrite a source artifact. Use namespaced asset paths so reusable apps do not collide on generic filenames such as style.css.

Verify discovery with an isolated temporary asset

The first complete program writes a synthetic CSS file inside its own TemporaryDirectory and asks a filesystem finder for the namespaced path. It prints the discovered filename rather than the random temporary parent. The file is removed when the context exits. This proves local discovery under one finder, not collection, CDN publication, browser caching, or application rendering. No project file or real static directory is touched. Keeping the fixture narrow makes a missing file, a wrong source path, and a later serving failure distinguishable instead of treating every 404 as the same staticfiles problem.

python Isolated executable example
from django.conf import settings
settings.configure(
    SECRET_KEY="synthetic-course-key",
    USE_I18N=False, USE_TZ=True,
    INSTALLED_APPS=[],
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
    DEFAULT_AUTO_FIELD="django.db.models.AutoField",
    ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()

from pathlib import Path
from tempfile import TemporaryDirectory
from django.contrib.staticfiles.finders import FileSystemFinder
from django.test import override_settings

with TemporaryDirectory() as directory:
    asset = Path(directory) / "catalog" / "style.css"
    asset.parent.mkdir()
    asset.write_text("body { color: navy; }", encoding="utf-8")
    with override_settings(STATICFILES_DIRS=[directory], STATIC_ROOT=None):
        found = FileSystemFinder().find("catalog/style.css")
        print(Path(found).name)
Expected output
style.css

The program creates and removes only its own temporary source asset. It tests filesystem discovery, not serving or collection.

Use the modern storage configuration intentionally

The second fragment uses Django 5.2’s STORAGES configuration with a default file storage and a manifest staticfiles backend. It is non-runnable application configuration, not a complete deployment. Manifest processing requires the reviewed asset build to produce the referenced hashed artifacts and consistent manifest. Templates should use the static tag under that contract rather than hard-coded hashed names. A storage setting does not by itself run collection or configure a reverse proxy. Keep an old application version’s referenced assets available during a compatible rollout when necessary, and distinguish cache invalidation from deleting artifacts still used by live clients.

python Application-context fragment
STATIC_URL = "/static/"
STATIC_ROOT = "/srv/training-app/static-artifact"
MEDIA_ROOT = "/srv/training-app/uploads"
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"},
}
Expected output
Configuration separates the collected static artifact from uploaded media and selects manifest-based static storage.

Illustrative Django 5.2 settings requiring the reviewed build, storage permissions, routing, and serving design. No directory is created and no collection or deployment operation is run.

Review the complete production boundary

Choose a production WSGI or ASGI server and reviewed process supervision; the development server is not the production solution. Coordinate settings, secrets, TLS, proxy trust, hosts, database compatibility, static/media delivery, timeouts, and recovery evidence. Deployment checks help identify configuration concerns but do not prove the service is secure, healthy, or restorable. Test asset references from rendered pages under the intended base path, cache behavior, unavailable media, and rollback to the previous artifact. This course demonstrates local parsing and discovery only; it never starts a project server, provisions external storage, or executes a configured application’s deployment commands.

Common errors and fixes

A production static URL returns 404 despite development success

Why it happens
Discovery and production serving were conflated.
How to fix it
Inspect build artifact, URL prefix, and actual serving route separately.
Prevent it next time
Test rendered references under the intended deployment path.

A manifest lookup fails after deployment

Why it happens
The manifest and referenced files do not match the released artifact.
How to fix it
Publish a consistent reviewed asset build.
Prevent it next time
Verify references before cutover and retain compatible old assets.

Uploads are served as trusted executable assets

Why it happens
User media shares the wrong trust boundary.
How to fix it
Separate media handling and serving policy.
Prevent it next time
Review content types, permissions, and storage ownership.

Check your understanding

1. Why does finding catalog/style.css locally not prove its public URL works?

Show worked solution

Discovery only establishes that a configured source finder can locate the asset. Collection, manifest processing, publication, server routing, URL prefix, and browser caching remain separate stages. Test those under the deployment’s reviewed artifact and request path rather than inferring them from the filesystem result.

2. What should be retained during a rollout when old HTML still references old hashed assets?

Show worked solution

Keep the required prior assets available for the documented compatibility window or coordinate cache and version lifetimes so clients are not stranded. Deleting every old artifact immediately can break otherwise healthy pages. Define retention and rollback with the release owner.

Key takeaways

  • Discovery, collection, and serving are different.
  • Uploads are not trusted application assets.
  • Publish compatible, reviewable artifacts.

Official references