Back to Blog
Tutorial10 min read2026-06-28

How to Deploy Directus Headless CMS to Production

Directus wraps any SQL database in an instant REST and GraphQL API plus an admin app. This guide covers deploying the official Docker image with PostgreSQL, secrets, caching, and file storage for production.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Directus is unusual among headless CMSes: instead of defining content models in code, it introspects an existing SQL database and gives you an instant API and admin UI on top. That makes deployment mostly a matter of pointing it at a database and configuring it correctly through environment variables. The official Docker image does the heavy lifting.

How Directus is configured

Directus has effectively zero config files for a basic deploy — everything is environment variables. The essentials:

# Database
DB_CLIENT=pg
DB_CONNECTION_STRING=${DATABASE_URL}

# Security (generate strong random values)
KEY=<random>
SECRET=<random>

# First admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=<strong-password>

# Public URL (needed for OAuth redirects, asset URLs)
PUBLIC_URL=https://cms.example.com

Generate KEY and SECRET:

openssl rand -base64 32   # KEY
openssl rand -base64 32   # SECRET

SECRET signs access tokens; if it changes, all sessions are invalidated. Treat it as a stable, protected secret.

The official image

You rarely need a custom Dockerfile — pin the official image by version:

# docker-compose.yml (for local parity)
services:
  directus:
    image: directus/directus:11
    ports: ['8055:8055']
    environment:
      DB_CLIENT: pg
      DB_CONNECTION_STRING: ${DATABASE_URL}
      KEY: ${KEY}
      SECRET: ${SECRET}
      ADMIN_EMAIL: ${ADMIN_EMAIL}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      PUBLIC_URL: ${PUBLIC_URL}

Directus runs migrations on its own database tables automatically at boot, so there is no separate migration step for the system schema. Your *content* tables are just regular SQL tables you can manage however you like.

File storage

Like any container app, Directus must not store uploads on local disk. Configure an S3-compatible backend:

STORAGE_LOCATIONS=s3
STORAGE_S3_DRIVER=s3
STORAGE_S3_KEY=<key>
STORAGE_S3_SECRET=<secret>
STORAGE_S3_BUCKET=my-directus-assets
STORAGE_S3_REGION=us-east-1
STORAGE_S3_ENDPOINT=https://s3.amazonaws.com

This works with AWS S3, Cloudflare R2, or MinIO. Without it, every container restart loses uploaded files.

Caching and rate limiting with Redis

For anything beyond a single replica, add Redis so caching and rate limiting are shared:

CACHE_ENABLED=true
CACHE_STORE=redis
REDIS=${REDIS_URL}
RATE_LIMITER_ENABLED=true
RATE_LIMITER_STORE=redis

With Redis, you can run multiple Directus replicas behind a load balancer and they will share cache and rate-limit state. Without it, each replica caches independently and rate limits are per-instance.

Deploying on PandaStack

Directus's environment-driven design fits a managed platform nicely:

  1. 1Provision a managed PostgreSQL instance. PandaStack injects DATABASE_URL; map it to DB_CONNECTION_STRING.
  2. 2Optionally provision Redis for caching and rate limiting in multi-replica setups.
  3. 3Deploy directus/directus:11 as a container app (point the service at the public image, or use a thin Dockerfile that FROMs it).
  4. 4Set KEY, SECRET, ADMIN_EMAIL, ADMIN_PASSWORD, PUBLIC_URL, and the S3 variables.
  5. 5Expose port 8055, attach a custom domain, and let SSL provision automatically.

A thin Dockerfile if your platform builds from a repo:

FROM directus/directus:11

That's it — all behavior comes from environment variables.

Production checklist

  • [ ] Stable KEY and SECRET (rotating them logs everyone out).
  • [ ] S3-compatible storage configured.
  • [ ] PUBLIC_URL set to the real external URL.
  • [ ] Redis for cache + rate limiting if running >1 replica.
  • [ ] Pin the image to a major version, not latest.
  • [ ] Change or remove ADMIN_PASSWORD after first boot and manage admins in-app.

Verifying

# Server info / health
curl -s https://cms.example.com/server/health

# Admin app
curl -s -o /dev/null -w '%{http_code}' https://cms.example.com/admin

A healthy /server/health and a 200 on /admin confirm the database connection and asset config are good.

References

  • [Directus environment variables reference](https://docs.directus.io/self-hosted/config-options.html)
  • [Directus Docker guide](https://docs.directus.io/self-hosted/docker-guide.html)
  • [Directus file storage configuration](https://docs.directus.io/self-hosted/config-options.html#file-storage)
  • [Directus caching docs](https://docs.directus.io/self-hosted/config-options.html#cache)

---

Directus plus a managed PostgreSQL is a two-step deploy on PandaStack, with DATABASE_URL injected and SSL handled for you. Start free at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also