Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/directus/directus/llms.txt

Use this file to discover all available pages before exploring further.

Docker provides a consistent, containerized way to deploy Directus across any environment. This guide covers deploying Directus with Docker and Docker Compose.

Quick Start with Docker

Using Official Image

Run Directus with SQLite (development only):
docker run -p 8055:8055 \
  -e SECRET="replace-with-secure-random-value" \
  -e ADMIN_EMAIL="admin@example.com" \
  -e ADMIN_PASSWORD="d1r3ctu5" \
  directus/directus
Access Directus at http://localhost:8055

With External Database

Run Directus with PostgreSQL:
docker run -p 8055:8055 \
  -e SECRET="replace-with-secure-random-value" \
  -e ADMIN_EMAIL="admin@example.com" \
  -e ADMIN_PASSWORD="d1r3ctu5" \
  -e DB_CLIENT="postgres" \
  -e DB_HOST="your-database-host" \
  -e DB_PORT="5432" \
  -e DB_DATABASE="directus" \
  -e DB_USER="postgres" \
  -e DB_PASSWORD="your-password" \
  directus/directus

Docker Compose

Docker Compose is the recommended approach for production deployments.

Basic Setup

Create docker-compose.yml:
version: '3.8'

services:
  directus:
    image: directus/directus:latest
    ports:
      - 8055:8055
    environment:
      SECRET: 'replace-with-secure-random-value'
      ADMIN_EMAIL: 'admin@example.com'
      ADMIN_PASSWORD: 'd1r3ctu5'
      DB_CLIENT: 'postgres'
      DB_HOST: 'database'
      DB_PORT: '5432'
      DB_DATABASE: 'directus'
      DB_USER: 'directus'
      DB_PASSWORD: 'directus'
    volumes:
      - ./uploads:/directus/uploads
      - ./extensions:/directus/extensions
    depends_on:
      - database

  database:
    image: postgis/postgis:13-3.4-alpine
    environment:
      POSTGRES_USER: 'directus'
      POSTGRES_PASSWORD: 'directus'
      POSTGRES_DB: 'directus'
    volumes:
      - ./data:/var/lib/postgresql/data

Production Setup

Complete production setup with Redis, PostgreSQL, and persistence:
version: '3.8'

services:
  directus:
    image: directus/directus:latest
    ports:
      - 8055:8055
    environment:
      # Security
      SECRET: '${SECRET}'
      
      # Database
      DB_CLIENT: 'postgres'
      DB_HOST: 'database'
      DB_PORT: '5432'
      DB_DATABASE: 'directus'
      DB_USER: '${DB_USER}'
      DB_PASSWORD: '${DB_PASSWORD}'
      
      # Admin User (first run only)
      ADMIN_EMAIL: '${ADMIN_EMAIL}'
      ADMIN_PASSWORD: '${ADMIN_PASSWORD}'
      
      # General
      PUBLIC_URL: '${PUBLIC_URL}'
      
      # Cache & Rate Limiting
      CACHE_ENABLED: 'true'
      CACHE_STORE: 'redis'
      REDIS: 'redis://cache:6379'
      
      # Storage
      STORAGE_LOCATIONS: 'local'
      STORAGE_LOCAL_ROOT: '/directus/uploads'
      
      # Email
      EMAIL_FROM: '${EMAIL_FROM}'
      EMAIL_TRANSPORT: 'smtp'
      EMAIL_SMTP_HOST: '${EMAIL_SMTP_HOST}'
      EMAIL_SMTP_PORT: '${EMAIL_SMTP_PORT}'
      EMAIL_SMTP_USER: '${EMAIL_SMTP_USER}'
      EMAIL_SMTP_PASSWORD: '${EMAIL_SMTP_PASSWORD}'
      
      # Performance
      RATE_LIMITER_ENABLED: 'true'
      RATE_LIMITER_STORE: 'redis'
    volumes:
      - ./uploads:/directus/uploads
      - ./extensions:/directus/extensions
    restart: unless-stopped
    depends_on:
      - database
      - cache
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://localhost:8055/server/health').then(r => r.ok || process.exit(1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  database:
    image: postgis/postgis:13-3.4-alpine
    environment:
      POSTGRES_USER: '${DB_USER}'
      POSTGRES_PASSWORD: '${DB_PASSWORD}'
      POSTGRES_DB: 'directus'
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:6-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

volumes:
  db-data:
Create a .env file for sensitive values:
# Security
SECRET=replace-with-secure-random-value-min-32-chars

# Database
DB_USER=directus
DB_PASSWORD=your-secure-db-password

# Admin User
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your-secure-admin-password

# General
PUBLIC_URL=https://your-domain.com

# Email
EMAIL_FROM=noreply@example.com
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=your-smtp-user
EMAIL_SMTP_PASSWORD=your-smtp-password

Start Services

1

Start Containers

docker compose up -d
2

View Logs

docker compose logs -f directus
3

Access Directus

Open http://localhost:8055 in your browser and log in with your admin credentials.

Dockerfile Reference

The official Directus Dockerfile uses a multi-stage build:
ARG NODE_VERSION=22

# Build stage
FROM node:${NODE_VERSION}-alpine AS builder

RUN npm install --global corepack@latest
RUN apk --no-cache add python3 py3-setuptools build-base

WORKDIR /directus

COPY package.json .
RUN corepack enable && corepack prepare

RUN chown node:node .
USER node

ENV NODE_OPTIONS=--max-old-space-size=8192

COPY pnpm-lock.yaml .
RUN pnpm fetch

COPY --chown=node:node . .
RUN pnpm install --recursive --offline --frozen-lockfile && \
    npm_config_workspace_concurrency=2 pnpm run build && \
    pnpm --filter directus deploy --legacy --prod dist

# Production stage
FROM node:${NODE_VERSION}-alpine AS runtime

RUN npm install --global pm2@5 corepack@latest

USER node
WORKDIR /directus

ENV DB_CLIENT="sqlite3" \
    DB_FILENAME="/directus/database/database.sqlite" \
    NODE_ENV="production" \
    NPM_CONFIG_UPDATE_NOTIFIER="false"

COPY --from=builder --chown=node:node /directus/ecosystem.config.cjs .
COPY --from=builder --chown=node:node /directus/dist .

EXPOSE 8055

CMD node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs

Persistent Storage

Ensure data persists across container restarts:

Database Data

services:
  database:
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

File Uploads

services:
  directus:
    volumes:
      - ./uploads:/directus/uploads

Extensions

services:
  directus:
    volumes:
      - ./extensions:/directus/extensions

Using S3 for Storage

Recommended for production to avoid container storage:
services:
  directus:
    environment:
      STORAGE_LOCATIONS: 's3'
      STORAGE_S3_DRIVER: 's3'
      STORAGE_S3_KEY: '${S3_ACCESS_KEY}'
      STORAGE_S3_SECRET: '${S3_SECRET_KEY}'
      STORAGE_S3_BUCKET: '${S3_BUCKET}'
      STORAGE_S3_REGION: 'us-east-1'

Advanced Configurations

Custom Extensions

Mount your extensions directory:
services:
  directus:
    volumes:
      - ./extensions:/directus/extensions
    environment:
      EXTENSIONS_AUTO_RELOAD: 'true'

WebSockets Support

Enable WebSockets for real-time features:
services:
  directus:
    environment:
      WEBSOCKETS_ENABLED: 'true'
      WEBSOCKETS_REST_ENABLED: 'true'
      WEBSOCKETS_REST_PATH: '/websocket'

Multiple Storage Locations

services:
  directus:
    environment:
      STORAGE_LOCATIONS: 'local,s3'
      STORAGE_LOCAL_DRIVER: 'local'
      STORAGE_LOCAL_ROOT: '/directus/uploads'
      STORAGE_S3_DRIVER: 's3'
      STORAGE_S3_KEY: '${S3_ACCESS_KEY}'
      STORAGE_S3_SECRET: '${S3_SECRET_KEY}'
      STORAGE_S3_BUCKET: '${S3_BUCKET}'
      STORAGE_S3_REGION: 'us-east-1'

Reverse Proxy with Nginx

Add Nginx for SSL termination:
services:
  nginx:
    image: nginx:alpine
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - directus

  directus:
    expose:
      - 8055
    # Remove ports section - accessed via nginx
Create nginx.conf:
http {
  upstream directus {
    server directus:8055;
  }

  server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
  }

  server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    location / {
      proxy_pass http://directus;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_cache_bypass $http_upgrade;
    }
  }
}

Docker Management

Useful Commands

# Start services
docker compose up -d

# Stop services
docker compose down

# View logs
docker compose logs -f

# Restart Directus
docker compose restart directus

# Execute commands in container
docker compose exec directus /bin/sh

# Update to latest image
docker compose pull
docker compose up -d

Database Backups

# Backup PostgreSQL
docker compose exec database pg_dump -U directus directus > backup.sql

# Restore PostgreSQL
docker compose exec -T database psql -U directus directus < backup.sql

Monitor Resources

# View resource usage
docker compose stats

# View running containers
docker compose ps

Scaling with Docker Swarm

Deploy multiple Directus instances:
version: '3.8'

services:
  directus:
    image: directus/directus:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
    environment:
      # Must use Redis for shared cache
      CACHE_STORE: 'redis'
      REDIS: 'redis://cache:6379'
      # Must use external storage (not local)
      STORAGE_LOCATIONS: 's3'

Troubleshooting

Container Won’t Start

# Check logs for errors
docker compose logs directus

# Verify environment variables
docker compose config

Database Connection Failed

# Ensure database is ready
docker compose logs database

# Test connection
docker compose exec directus ping -c 3 database

Permission Issues

# Fix upload directory permissions
sudo chown -R 1000:1000 ./uploads
sudo chmod -R 755 ./uploads

Out of Memory

Increase memory limits:
services:
  directus:
    deploy:
      resources:
        limits:
          memory: 2G

Security Best Practices

  1. Don’t expose database ports - Keep database internal to Docker network
  2. Use secrets - Store sensitive data in .env file, never commit to Git
  3. Update regularly - Pull latest images for security patches
  4. Use specific versions - Pin image versions in production (e.g., directus/directus:10.10.0)
  5. Enable health checks - Ensure containers restart on failure
  6. Limit resources - Prevent containers from consuming all host resources

Next Steps