Backend & CMS

    Deploy Directus on a VPS

    Self-host the open-source headless CMS with Docker Compose, PostgreSQL, Redis caching, Nginx, and free SSL.

    Prerequisites

    • A RamNode VPS running Ubuntu 22.04 (the $4/month SSD VPS is sufficient for personal projects; 2 GB RAM recommended for production)
    • SSH access as a non-root user with sudo privileges
    • A domain name with an A record pointing to your server's IP address
    • Basic comfort with the Linux command line

    Licensing note: Directus is free to self-host for any organization with less than $5 million in total annual revenue and funding. No license required for the vast majority of self-hosters.

    1

    Initial Server Setup

    Log into your VPS and apply system updates:

    Update system
    sudo apt update && sudo apt upgrade -y

    Create a dedicated deployment directory:

    Create directories
    mkdir -p ~/directus && cd ~/directus
    mkdir uploads extensions
    2

    Install Docker and Docker Compose

    Install Docker via the official convenience script:

    Install Docker
    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker $USER
    newgrp docker

    Verify the installation:

    Verify
    docker --version
    docker compose version
    3

    Generate a Secure Secret

    Directus requires a SECRET environment variable to sign tokens. Generate a strong random value:

    Generate secret
    openssl rand -hex 32

    Copy this value — you'll use it in the next step.

    4

    Create the Docker Compose File

    Inside ~/directus, create your docker-compose.yml:

    docker-compose.yml
    services:
      database:
        image: postgres:15-alpine
        restart: unless-stopped
        volumes:
          - ./data/database:/var/lib/postgresql/data
        environment:
          POSTGRES_USER: "directus"
          POSTGRES_PASSWORD: "CHANGE_THIS_DB_PASSWORD"
          POSTGRES_DB: "directus"
        healthcheck:
          test: ["CMD", "pg_isready", "--host=localhost", "--username=directus"]
          interval: 10s
          timeout: 5s
          retries: 5
    
      cache:
        image: redis:7-alpine
        restart: unless-stopped
        healthcheck:
          test: ["CMD-SHELL", "[ $(redis-cli ping) = 'PONG' ]"]
          interval: 10s
          timeout: 5s
          retries: 5
    
      directus:
        image: directus/directus:latest
        restart: unless-stopped
        ports:
          - "127.0.0.1:8055:8055"
        volumes:
          - ./uploads:/directus/uploads
          - ./extensions:/directus/extensions
        depends_on:
          database:
            condition: service_healthy
          cache:
            condition: service_healthy
        environment:
          SECRET: "PASTE_YOUR_GENERATED_SECRET_HERE"
    
          DB_CLIENT: "pg"
          DB_HOST: "database"
          DB_PORT: "5432"
          DB_DATABASE: "directus"
          DB_USER: "directus"
          DB_PASSWORD: "CHANGE_THIS_DB_PASSWORD"
    
          CACHE_ENABLED: "true"
          CACHE_AUTO_PURGE: "true"
          CACHE_STORE: "redis"
          REDIS: "redis://cache:6379"
    
          ADMIN_EMAIL: "admin@yourdomain.com"
          ADMIN_PASSWORD: "CHANGE_THIS_ADMIN_PASSWORD"
    
          PUBLIC_URL: "https://yourdomain.com"

    Important: Replace CHANGE_THIS_DB_PASSWORD, CHANGE_THIS_ADMIN_PASSWORD, PASTE_YOUR_GENERATED_SECRET_HERE, and yourdomain.com with your actual values before starting the stack.

    5

    Start the Stack

    Start containers
    docker compose up -d

    Check that all three containers are running:

    Check status
    docker compose ps

    Watch the Directus startup logs to confirm the database bootstrapped cleanly:

    View logs
    docker compose logs -f directus

    You should see a line similar to Server started at http://0.0.0.0:8055 when it's ready.

    6

    Install and Configure Nginx

    Install Nginx:

    Install Nginx
    sudo apt install nginx -y

    Create a server block for your domain:

    /etc/nginx/sites-available/directus
    server {
        listen 80;
        server_name yourdomain.com;
    
        client_max_body_size 100M;
    
        location / {
            proxy_pass http://127.0.0.1:8055;
            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;
        }
    }

    Enable the site and test:

    Enable and reload
    sudo ln -s /etc/nginx/sites-available/directus /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx
    7

    Enable SSL with Let's Encrypt

    Install Certbot:

    Install Certbot
    sudo apt install certbot python3-certbot-nginx -y

    Obtain and install a certificate:

    Get certificate
    sudo certbot --nginx -d yourdomain.com

    Verify the renewal timer is active:

    Check renewal
    sudo systemctl status certbot.timer
    8

    Open Firewall Ports

    If you have ufw enabled, allow HTTP and HTTPS traffic:

    Allow traffic
    sudo ufw allow 'Nginx Full'
    sudo ufw status
    9

    Access the Admin Panel

    Navigate to https://yourdomain.com in your browser. Log in with the ADMIN_EMAIL and ADMIN_PASSWORD you set in your Compose file. You'll land in the Directus Studio where you can start building your data model.

    10

    Configure Automatic Backups (Recommended)

    Back up the PostgreSQL database on a daily schedule using a cron job:

    Create backup directory
    mkdir -p ~/backups
    crontab -e

    Add this line to run a backup at 2 AM every day:

    Daily backup cron
    0 2 * * * docker exec directus-database-1 pg_dump -U directus directus | gzip > /home/$(whoami)/backups/directus-$(date +\%Y\%m\%d).sql.gz

    To keep only the last 14 days of backups:

    Cleanup cron
    0 3 * * * find /home/$(whoami)/backups -name "*.sql.gz" -mtime +14 -delete

    Confirm your container name with docker compose ps — it may differ slightly depending on your directory name.

    Keeping Directus Updated

    To pull the latest Directus image and restart the stack:

    Update Directus
    cd ~/directus
    docker compose pull
    docker compose up -d

    Always review the Directus release notes before major version upgrades, and take a database backup first.

    Resource Sizing Guide

    PlanRAMUse Case
    SSD VPS 1 ($4/mo)512 MBDevelopment, low-traffic personal projects
    SSD VPS 2 ($8/mo)1 GBSmall production sites, single team
    SSD VPS 3 ($16/mo)2 GBRecommended minimum for production
    SSD VPS 4+ ($32/mo+)4 GB+Multi-user apps, heavy asset management

    Directus officially recommends a minimum of 2 vCPU / 2 GB RAM for production workloads. The Redis cache layer in this guide significantly reduces database load and improves response times, making smaller VPS tiers viable for moderate traffic.

    Troubleshooting

    Directus container won't start

    Check logs with docker compose logs directus. The most common cause is a malformed environment variable or the database not being healthy before Directus tries to connect.

    502 Bad Gateway from Nginx

    Directus may still be initializing. Wait 30–60 seconds after docker compose up before testing, then check docker compose ps to confirm all containers are running.

    Uploads not persisting

    Confirm the ./uploads volume mount is correct in your Compose file and that the directory exists on the host with proper permissions.

    Out of disk space

    Docker image layers accumulate over time. Clean up unused images and containers periodically with docker system prune.

    Next Steps

    • Connect a frontend framework like Next.js, Nuxt, or SvelteKit to your Directus API
    • Configure role-based access control to restrict what authenticated users can read and write
    • Set up Directus Flows for webhooks and automations
    • Explore S3-compatible object storage for uploads — RamNode's block storage keeps costs low as your media library grows