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.

django CMS in productionApp imageGunicornPostgreSQLpages, pluginsObject storagefiler mediaRedis cachepages, menusEditors change content in the database and media store, so both need backups.
  1. Use PostgreSQL, not SQLite, and keep migrations for cms, menus, djangocms_*, and filer in your deploy step.
  2. Store django-filer files on S3-compatible storage with django-storages; thumbnails go there too.
  3. Configure a shared cache (Redis) so CMS page, placeholder, and menu caches work across workers.
  4. Serve static files with WhiteNoise or a CDN after collectstatic.
  5. 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.

data and how to protect itStored inBackup?Pages, plugins, treePostgreSQL!yes, PITRImages and filesobject storage!yes, versioningThumbnailsobject storageregenerableRendered outputRedis cachenoContent lives in two places, and both need backupsdjango-deployment.com
A database restore without the matching media leaves broken images.

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 check after 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.

safe order for CMS changes1Add new template or pluginkeep the old one2Deploy with migrationsstaging first3Editors move contentold to new4Remove old in later releasecms check passes5Clear CMS cachesafter each releaseContent in the database depends on templates and plugins in the codedjango-deployment.com
Removing code that content still references is the most common CMS outage.

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_ID and 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

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.

Keep going