Directus wraps any SQL database with a real-time REST and GraphQL API, plus an intuitive admin interface for non-technical users. Unlike code-first CMS solutions, Directus works with your existing database schema and adds an API layer on top. This guide covers deploying Directus on a VPS.
Installation with Docker
# docker-compose.yml
services:
directus:
image: directus/directus:11
ports:
- "8055:8055"
environment:
SECRET: "your-random-secret-string"
DB_CLIENT: "pg"
DB_HOST: "postgres"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "directuspassword"
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "securepassword"
PUBLIC_URL: "https://cms.example.com"
CACHE_ENABLED: "true"
CACHE_STORE: "redis"
REDIS: "redis://redis:6379"
volumes:
- uploads:/directus/uploads
- extensions:/directus/extensions
depends_on:
- postgres
- redis
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: directus
POSTGRES_USER: directus
POSTGRES_PASSWORD: directuspassword
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:
uploads:
extensions:
# Start
docker compose up -d
# Access admin at http://localhost:8055
Using the API
# REST API
# List items
curl https://cms.example.com/items/articles \
-H "Authorization: Bearer YOUR_TOKEN"
# Create item
curl -X POST https://cms.example.com/items/articles \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Hello World", "content": "My first article"}'
# GraphQL
curl -X POST https://cms.example.com/graphql \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ articles { id title content } }"}'
# Real-time with WebSocket
# Connect to wss://cms.example.com/websocket
JavaScript SDK
import { createDirectus, rest, readItems, createItem } from "@directus/sdk";
const client = createDirectus("https://cms.example.com").with(rest());
// Fetch articles
const articles = await client.request(
readItems("articles", {
filter: { status: { _eq: "published" } },
sort: ["-date_published"],
limit: 20,
})
);
// Create article
await client.request(
createItem("articles", {
title: "New Article",
content: "Content here",
status: "draft",
})
);
Collections and Fields
# Create collections via Admin UI or API:
# Settings > Data Model > Create Collection
# Or via migration/snapshot
# npx directus schema snapshot ./snapshot.yaml
# npx directus schema apply ./snapshot.yaml
# Field types available:
# - Text (input, textarea, WYSIWYG, Markdown)
# - Numeric (integer, decimal, slider)
# - Boolean (toggle, checkbox)
# - DateTime (date, time, timestamp)
# - Selection (dropdown, radio, checkboxes)
# - Relational (M2O, O2M, M2M, M2A)
# - File/Image upload
# - JSON
# - Translations
Roles and Permissions
# Directus has granular CRUDS permissions per collection per role:
# C - Create
# R - Read
# U - Update
# D - Delete
# S - Share (file sharing)
# Configure via Admin UI:
# Settings > Roles & Permissions
# Create custom roles: Editor, Author, Viewer
# Set field-level and item-level permissions
# Use custom filters for row-level security
Flows (Automation)
# Directus Flows provide visual automation:
# - Trigger: item.create, item.update, schedule, webhook
# - Operations: send email, run script, HTTP request, transform data
#
# Example: Send email on new contact form submission
# Trigger: items.create (contacts collection)
# Operation 1: Send Email
# To: admin@example.com
# Subject: New Contact: {{$trigger.payload.name}}
# Body: {{$trigger.payload.message}}
Nginx Reverse Proxy
server {
listen 443 ssl http2;
server_name cms.example.com;
ssl_certificate /etc/letsencrypt/live/cms.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cms.example.com/privkey.pem;
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;
}
}
Summary
Directus is the most flexible open-source backend-as-a-service, working with any existing SQL database without migration. The admin UI is polished enough for non-technical content editors, while the REST/GraphQL API and JavaScript SDK serve developers. Flows provide no-code automation, and the granular permission system handles complex multi-tenant scenarios. For teams that want a Supabase-like experience they can self-host, Directus on a VPS is the strongest option.