Deploy django CMS to Production
Deploy a django CMS site to production: configure settings for CMS and filer, PostgreSQL, media on object storage, static files, caching of rendered pages and menus, search engine friendly URLs, and a release process for template and plugin changes.
Problem statement
A django CMS site behaves like any Django project with a few extra demands: editors upload images and files through django-filer, pages are built from plugins and rendered through templates with heavy use of caching, and the page tree and placeholders live in the database. Deploying it well means keeping uploaded files outside the container, caching rendered content without serving stale pages to editors, and coordinating template and plugin changes with database migrations.
- Use PostgreSQL, not SQLite, and keep migrations for
cms,menus,djangocms_*, andfilerin your deploy step. - Store django-filer files on S3-compatible storage with django-storages; thumbnails go there too.
- Configure a shared cache (Redis) so CMS page, placeholder, and menu caches work across workers.
- Serve static files with WhiteNoise or a CDN after
collectstatic. - Deploy template and plugin changes together with their migrations, and clear CMS caches after releases.
Step-by-step solution
1. Production settings
INSTALLED_APPS = [
# ...
"cms", "menus", "treebeard", "sekizai",
"filer", "easy_thumbnails",
"djangocms_text", "djangocms_frontend",
"storages",
]
DATABASES = {"default": dj_database_url.config(conn_max_age=60, conn_health_checks=True)}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": os.environ["REDIS_URL"],
"KEY_PREFIX": "cms",
}
}
CMS_CACHE_DURATIONS = {"content": 300, "menus": 3600, "permissions": 3600}
SITE_ID = 1
LANGUAGES = [("en", "English")]
Package names differ between django CMS versions (for example djangocms_text replacing older text plugins); match them to the version you run. With multiple workers, a local-memory cache would show different content on different workers after edits; a shared cache keeps them consistent.
2. Media with django-filer
django-filer stores uploads through Django's default storage. Point it at object storage:
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {"bucket_name": "acme-cms-media", "region_name": "eu-central-1",
"default_acl": None, "querystring_auth": False,
"custom_domain": "media.example.com"},
},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
THUMBNAIL_DEFAULT_STORAGE = "storages.backends.s3.S3Storage"
Public CMS images usually do not need presigned URLs; serve them through a CDN domain. Files marked private in filer need a separate private storage configuration.
3. Templates, plugins, and migrations
Page templates are listed in CMS_TEMPLATES, and existing pages reference them by name. Removing or renaming a template breaks pages that use it. Plugin models have migrations, and removing a plugin app leaves orphaned plugin rows. Treat these changes like schema changes:
- Add new templates and plugins first, deploy, let editors move content, then remove old ones in a later release.
- Run
python manage.py cms checkafter deploys to find problems such as missing templates or plugins.
4. Caching and editors
django CMS caches placeholder content and menus and invalidates them when editors publish. Behind a CDN, cache public pages for a short time and bypass the cache for logged-in users (the CMS toolbar sets cookies). Add a Vary: Cookie aware rule or bypass on the session cookie at the CDN. After a release that changes templates, clear the cache:
python manage.py shell -c "from django.core.cache import cache; cache.clear()"
Use a dedicated Redis database for the cache if sessions share the same Redis.
5. Deploy
Package the site as a Docker image, run collectstatic at build time, and run migrations before switching traffic. The same patterns as any Django app apply: Gunicorn behind a reverse proxy, environment-based settings, health checks, and a staging environment with a copy of production content for editors to review template changes.
Explanation
django CMS stores the page tree, placeholders, and plugin instances in the database, and renders them through templates and plugin classes in your code. That coupling is the main deployment risk: code and content must stay compatible. Media handled by django-filer is ordinary Django storage, so object storage works well. Caching is central to CMS performance, and a shared cache is required as soon as more than one process serves pages.
Edge cases / notes
- Multiple sites and languages. Each
SITE_IDand language adds cache keys and URL patterns; test with production settings. - Search. Indexing CMS pages requires extracting placeholder content; plan it as a background job.
- Upgrades. Major django CMS versions changed the data model (version 4 introduced a new content and versioning model). Rehearse upgrades on a copy of production data.
Next steps
- Compare with another CMS in Deploy Wagtail in Production.
- Set up media storage with Store Django Media Files on S3.
- Plan caching with Django Caching Strategy for Production.
FAQ
Can django CMS use SQLite in production?
It can for very small sites, but PostgreSQL handles concurrent editors and backups much better.
Where are uploaded images stored?
In django-filer, which uses Django's default storage. Configure object storage for production.
Why do editors see old content after publishing?
Usually a per-process cache. Use a shared Redis cache so all workers see invalidations.
How should I remove a page template?
Keep it until no pages use it, move content to the new template, then remove it in a later release.