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.
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.
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)
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.
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"},
}
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.