How to Deploy and Self-Host Directus CMS using Docker Container + Nginx Reverse Proxy
π€ Subhodip Ghosh β’
π
August 15, 2026 β’
ποΈ 20 views
β’ π Updated August 18, 2026
Most headless CMS platforms force your data into proprietary database structures and rigid schema abstractions. Directus works differently. It functions as an open-source data engine that sits directly on top of your existing relational database, inspecting your tables in real time to generate production-ready REST and GraphQL APIs alongside an admin Data Studio.
If you want full infrastructure control, self-hosting Directus gives you complete data privacy, custom extension support, and zero vendor lock-in.
This guide walks you through deploying Directus in production on any Linux server (such as Ubuntu, Debian, or Alpine) using Docker Compose, PostgreSQL 16, Redis caching, Nginx, and free Let's Encrypt SSL certificates.
---
## Why Self-Host Directus? Core Benefits
Self-hosting Directus on your own infrastructure offers several major engineering and business advantages over proprietary SaaS content platforms:
* **Direct SQL Database Mirroring (Zero Vendor Lock-In):** Directus does not hide your data behind custom proprietary database schemas or proprietary ORMs. It mirrors your existing PostgreSQL, MySQL, or MS SQL tables directly. If you ever decide to remove Directus, your underlying database remains clean, standard SQL.
* **Predictable Cost & Unlimited Scale:** SaaS headless CMS platforms bill you per API request, per content item, or per admin user. Self-hosting Directus means your operational costs remain tied strictly to your VPS hardware resources, regardless of whether you serve 10,000 or 10,000,000 API calls per month.
* **Full Data Sovereignty & Strict Compliance:** By hosting Directus on your own private cloud or on-premise servers, you maintain complete custody over your data. This is essential for organizations adhering to GDPR, HIPAA, SOC2, or strict internal data governance rules.
* **Unlimited Extensibility:** Self-hosting allows you to install custom Node.js extensions, custom API endpoints, custom display interfaces, and custom event hooks directly into your deployment directory without platform restrictions.
---
## 1. Understanding Directus Licensing: From Open Source to MSCL
Before setting up your server, it's important to understand how Directus handles licensing for self-hosted instances. Directus is widely chosen because it is a free, self-hostable data platform, but its licensing model has evolved over time to balance open access with project sustainability.
### The Licensing Evolution: BSL to MSCL
* **Transition at Version 10.0.0:** On **April 26, 2023**, Directus introduced a source-available license starting with **Directus version 10.0.0**, moving away from fully permissive open-source terms for certain commercial use cases by adopting the **Business Source License (BSL)**.
* **Current Licensing Model (MSCL):** Directus has since evolved its structure to the **Monospace Sustainable Core License (MSCL)** (currently governing Directus v12). This model ensures the core platform remains free for most self-hosters while monetizing large-scale enterprise deployments.
### Core Tier vs. Open Innovation Grant (OIG) Comparison
The table below breaks down the specific feature caps, licensing keys, and eligibility across Directus self-hosting tiers:
| Feature / Capability | Core Tier (Free, No Key) | Open Innovation Grant (OIG) (Free Key) | Enterprise Tier (Paid Key) |
| :--- | :--- | :--- | :--- |
| **Eligibility Threshold** | Available to everyone | Revenue < $5M USD & < 50 Employees | Revenue > $5M USD or > 50 Employees |
| **License Key Required?** | β No registration key needed | β
Free registration key required | β
Paid commercial key required |
| **User Seats Limit** | Max 3 Admin Seats | **Unlimited Seats** | **Unlimited Seats** |
| **Collection Cap** | Max 50 Collections | **Unlimited Collections** | **Unlimited Collections** |
| **Single Sign-On (SSO)** | β Standard Auth Only | β
OAuth2, OpenID, SAML, LDAP | β
OAuth2, OpenID, SAML, LDAP |
| **RBAC Permissions** | Basic Role Rules | **Advanced Field & Filter Rules** | **Advanced Field & Filter Rules** |
| **Allowed Concurrent URLs** | N/A (Local / Self-hosted) | Up to 5 Static Domains per key | Custom / Flexible |
> **Key Takeaway:** If your business makes under $5,000,000 USD in annual revenue and has under 50 employees, requesting a free OIG key from the Directus website unlocks the full, unrestricted platform for free.
---
## 2. Directus Architecture Explained
Understanding how Directus handles data flow makes debugging and scaling much easier down the road.
Rather than running as a standalone content engine with its own internal data structures, Directus acts as a **Database Mirroring Engine**.
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client / App β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β (REST / GraphQL / WebSockets)
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Nginx Reverse Proxy (SSL) β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β (HTTP Port 8055)
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Directus API (Node.js Engine) β
β βββββββββββββββββββββββββ΄βββββββββββββββββββββββββββ β
β β Redis Cache Engine β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β (Native Database Protocol)
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PostgreSQL Database Layer β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
```
The system comprises four core layers:
1. **Database Layer:** Directus sits directly on top of your SQL schema. If you execute a raw SQL migration to add a column or table, Directus detects the schema update instantly without sync delays.
2. **API Engine (Node.js):** Generates REST and GraphQL endpoints dynamically based on your database tables, handles JWT authentication, processes uploads, and manages WebSocket connections.
3. **Admin Data Studio (Vue.js):** A decoupled Single-Page Application (SPA) that consumes the generated REST API to provide a no-code management interface for team members.
4. **Cache Layer (Redis):** Caches schema metadata, role permissions, and frequent API queries to reduce database read load and lower response latencies.
---
## 3. System & Hardware Requirements
Here are the baseline requirements for running a self-hosted Directus instance on Linux:
### Recommended Hardware Specs
* **Testing / Development:** 1 vCPU, 1 GB RAM, 10 GB NVMe/SSD.
* **Production Standard:** 2 vCPUs, 2 GB RAM (4 GB recommended if processing heavy asset transformations or running custom extensions), 20+ GB NVMe/SSD.
* **Media Storage:** If your application handles large image galleries or high-definition video, route storage to an external S3-compatible service (such as AWS S3 or Cloudflare R2).
### Software & Network Prerequisites
* **Operating System:** Any modern Linux distribution (Ubuntu 22.04/24.04, Debian 12, Alpine, RHEL, etc. - Debian/Ubuntu commands used in examples).
* **Supported Databases:** PostgreSQL 12+ (recommended for production), MySQL 8+, MS SQL Server, or SQLite (dev/homelab only).
* **Firewall Rules:** Open public HTTP (`80`) and HTTPS (`443`) ports. Ensure internal container ports (`8055` for Directus, `5432` for PostgreSQL) remain blocked from external access.
---
## 4. Server Preparation & Docker Setup
Start by logging into your server over SSH:
```bash
ssh root@your_server_ip
```
Update your system packages and install prerequisites:
```bash
# Update existing packages
sudo apt update && sudo apt upgrade -y
# Install required system tools
sudo apt install -y curl ca-certificates gnupg lsb-release
```
### Why Deploy Directus with Docker?
Containerizing Directus using Docker Compose provides significant production advantages over bare-metal Node.js installations:
* **Eliminates Node Environment Mismatches:** Directus runs in an isolated container image bundled with its exact Node.js runtime dependencies, avoiding conflicts with system-installed Node packages.
* **Private Network Isolation:** Docker Compose links Directus, PostgreSQL, and Redis inside a private internal virtual bridge network (`directus_network`). Database ports (`5432`) and cache ports (`6379`) are completely isolated from the host server's public network interface.
* **Atomic Version Upgrades & Quick Rollbacks:** Upgrading Directus requires running `docker compose pull` and restarting the stack. If a new release exhibits issues, rolling back to the previous image tag takes seconds.
* **Unprivileged Non-Root Execution:** The official Directus container runs under an unprivileged user (`node` with UID `1000`), restricting the blast radius if an application vulnerability occurs.
### Installing Docker Engine & Docker Compose V2
Add Docker's official GPG key and package repository:
```bash
# Add Docker GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Register Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```
Install Docker Engine and Docker Compose V2:
```bash
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Verify installation
docker compose version
```
---
## 5. Project Directory & Host File Permissions
Create a main deployment folder on your host machine to store configuration files, upload directories, and custom extensions:
```bash
mkdir -p /opt/directus
cd /opt/directus
mkdir -p uploads extensions
```
### Fixing File Ownership (UID 1000 Pitfall)
The official Directus Docker image drops root privileges and runs as an unprivileged container user (`node` with UID/GID `1000`). If you create the `uploads` and `extensions` directories while logged in as `root`, the Directus container will fail to write uploaded files, causing immediate runtime errors during media uploads.
Grant container owner rights to these host directories:
```bash
sudo chown -R 1000:1000 uploads extensions
```
### Generating Random Application Secrets
Directus uses `KEY` for telemetry identification and `SECRET` for signing authentication tokens and session cookies. Generate two distinct 64-character hex keys:
```bash
openssl rand -hex 32
openssl rand -hex 32
```
Save these output strings; you will paste them into your environment file.
---
## 6. Configuring Environment Variables (`.env`)
Create the `.env` configuration file inside `/opt/directus`:
```bash
nano .env
```
Paste in the following production settings, making sure to replace placeholder domains, generated keys, database credentials, and email settings:
```ini
# ===================================================
# Main Project Settings
# ===================================================
PUBLIC_URL="https://api.yourdomain.com"
KEY="your_first_generated_64_char_hex_secret"
SECRET="your_second_generated_64_char_hex_secret"
# Directus v12 License Key (OIG or Enterprise)
# Leave empty to run on the free Core tier (3 seats, 50 collections limit)
LICENSE_KEY="your_license_key_if_applicable"
# ===================================================
# Master Admin Credentials
# ===================================================
ADMIN_EMAIL="[email protected]"
ADMIN_PASSWORD="UseAStrongProductionPassword987!"
# ===================================================
# Database Connection (PostgreSQL)
# ===================================================
DB_CLIENT="pg"
DB_HOST="database"
DB_PORT="5432"
DB_DATABASE="directus"
DB_USER="directus_user"
DB_PASSWORD="UseAStrongDatabasePassword456!"
# ===================================================
# Cache Configuration (Redis)
# ===================================================
CACHE_ENABLED="true"
CACHE_STORE="redis"
CACHE_REDIS="redis://cache:6379"
# ===================================================
# Email Configuration (SMTP)
# ===================================================
EMAIL_TRANSPORT="smtp"
EMAIL_FROM="[email protected]"
EMAIL_SMTP_HOST="smtp.mailgun.org"
EMAIL_SMTP_PORT="587"
EMAIL_SMTP_USER="[email protected]"
EMAIL_SMTP_PASSWORD="your_smtp_password"
EMAIL_SMTP_SECURE="false"
# ===================================================
# Security & Upload Payload Configuration
# ===================================================
CORS_ENABLED="true"
CORS_ORIGIN="true"
MAX_PAYLOAD_SIZE="50mb"
# ===================================================
# Storage Settings (Local Disk)
# ===================================================
STORAGE_LOCATIONS="local"
STORAGE_LOCAL_ROOT="/directus/uploads"
# ===================================================
# Storage Settings (Optional S3 / Cloudflare R2)
# ===================================================
# STORAGE_LOCATIONS="s3"
# STORAGE_S3_DRIVER="s3"
# STORAGE_S3_KEY="your_r2_access_key_id"
# STORAGE_S3_SECRET="your_r2_secret_access_key"
# STORAGE_S3_BUCKET="your_bucket_name"
# STORAGE_S3_ENDPOINT="https://<account_id>.r2.cloudflarestorage.com"
# STORAGE_S3_REGION="us-east-1"
```
> **Important Notes:**
> * `PUBLIC_URL` must match your actual public HTTPS endpoint. Directus uses this variable for link construction, password resets, OAuth callbacks, and license activation validation.
> * If your passwords include special characters like `$`, `#`, `!`, or quotes, wrap them in double quotes within `.env` to prevent Docker Compose variable parsing issues.
---
## 7. Building the `docker-compose.yml` File
Create your `docker-compose.yml` file:
```bash
nano docker-compose.yml
```
Add the container configuration below, defining PostgreSQL 16, Redis 7, and Directus:
```yaml
services:
database:
image: postgres:16-alpine
container_name: directus_db
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- directus_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_DATABASE}"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
container_name: directus_cache
restart: unless-stopped
networks:
- directus_network
directus:
image: directus/directus:latest
container_name: directus_app
restart: unless-stopped
ports:
- "127.0.0.1:8055:8055"
depends_on:
database:
condition: service_healthy
cache:
condition: service_started
env_file:
- .env
volumes:
- ./uploads:/directus/uploads
- ./extensions:/directus/extensions
networks:
- directus_network
volumes:
pg_data:
networks:
directus_network:
driver: bridge
```
### Key Configuration Choices Explained
* **`pg_data` Volume:** Named Docker volumes protect PostgreSQL data from permission anomalies common with raw directory mounts on Linux hosts.
* **`127.0.0.1:8055:8055` Loopback Binding:** Binding Port 8055 strictly to `127.0.0.1` ensures external internet traffic cannot bypass Nginx to reach the Directus application directly.
* **PostgreSQL Healthcheck:** Directus automatically runs schema migrations on launch. `condition: service_healthy` keeps Directus paused until PostgreSQL is up and accepting connections, preventing boot crash loops.
* **Redis Volatility:** Redis operates purely as a transient cache layer. Container restarts will not affect core data stored in PostgreSQL.
---
## 8. Bootstrapping and Starting Directus
Launch the service stack in background mode:
```bash
docker compose up -d
```
Docker will download the required images, create the network bridge, and initialize the database containers.
Follow startup logs to watch schema migrations and initialization finish:
```bash
docker compose logs -f directus
```
Once you see the following log entry, Directus is up:
```
[INFO] Server started at http://0.0.0.0:8055
```
Press `Ctrl + C` to exit log streaming.
---
## 9. Setting Up Nginx Reverse Proxy & Let's Encrypt SSL
To serve Directus securely over HTTPS, configure Nginx to proxy incoming web traffic to container port `8055`.
### 1. Install Nginx and Certbot
```bash
sudo apt install -y nginx certbot python3-certbot-nginx
```
### 2. Configure the Nginx Server Block
Create a dedicated Nginx configuration file:
```bash
sudo nano /etc/nginx/sites-available/directus
```
Paste the server configuration below, replacing `api.yourdomain.com` with your subdomain:
```nginx
server {
server_name api.yourdomain.com;
# Set maximum client upload body size to match your .env setting
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:8055;
proxy_http_version 1.1;
# WebSocket support for Directus real-time subscriptions
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Proxy header forwarding
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;
# Timeout configurations for long migrations and heavy workloads
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
```
Enable the configuration, test for syntax errors, and reload Nginx:
```bash
sudo ln -s /etc/nginx/sites-available/directus /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
### 3. Provision Free SSL with Certbot
Obtain an SSL certificate using Certbot:
```bash
sudo certbot --nginx -d api.yourdomain.com
```
Select the option to automatically redirect HTTP traffic to HTTPS. Certbot will update your Nginx config automatically.
Run a dry-run renewal test to confirm systemd timers will auto-renew your certificates:
```bash
sudo certbot renew --dry-run
```
---
## 10. Verifying the Installation
Open your browser and navigate to:
```
https://api.yourdomain.com
```
You will see the Directus login interface. Log in using the `ADMIN_EMAIL` and `ADMIN_PASSWORD` defined in your `.env` file.
To verify API operation, access the health endpoint:
```
https://api.yourdomain.com/server/ping
```
A healthy instance returns:
```json
{
"status": 200,
"message": "pong"
}
```
---
## 11. Security Features & Infrastructure Hardening
Directus includes built-in security features to protect your data, but securing a self-hosted instance also requires hardening your server infrastructure.
### Native Directus Security Capabilities
* **Granular Role-Based Access Control (RBAC):** Define explicit permissions for Create, Read, Update, and Delete operations down to specific collections, individual fields, or conditional row-level rules (e.g., users can only view records where `author_id = $CURRENT_USER`).
* **Multi-Factor Authentication (MFA / 2FA):** Built-in support for Time-based One-Time Passwords (TOTP) compatible with Google Authenticator, Authy, or 1Password. MFA can be enforced per user or mandated globally for specific administrative roles.
* **Argon2id Password Hashing & JWT Security:** User credentials are encrypted using Argon2id (or Bcrypt). API authentication relies on short-lived JWT access tokens paired with secure HTTP-only refresh cookies.
* **Built-In API Rate Limiting:** Protect your endpoints against brute-force attacks and DDoS by enabling rate limiting in `.env`:
```ini
RATE_LIMIT_ENABLED="true"
RATE_LIMIT_POINTS="100" # Max 100 requests
RATE_LIMIT_DURATION="1m" # Per 1 minute window
```
### Server & Infrastructure Security Best Practices
1. **Configure UFW Firewall:** Restrict incoming server connections to essential web ports only:
```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
2. **Restrict CORS Origins:** In production, avoid using `CORS_ORIGIN="true"`. Restrict CORS to your exact frontend web domain in `.env`:
```ini
CORS_ORIGIN="https://yourfrontend.com"
```
3. **Isolate Database Access:** Always bind Directus container ports to loopback (`127.0.0.1:8055:8055`) and keep PostgreSQL port `5432` closed to the external internet.
---
## 12. Role-Based Access Control & API Tokens
By default, Directus restricts public access to all database collections. You can grant access and set up API credentials directly inside the Data Studio.
### Exposing Public Data
If you are building a public website, blog, or mobile app, configure read permissions for unauthenticated users:
1. Go to **Settings > Roles & Permissions > Public**.
2. Locate your collection under **Collections**.
3. Set permissions under the **Read** column to **All Access** (or define custom field/filter rules).
4. Save your changes. Anonymous client requests can now query these endpoints.
### Static API Tokens for Applications
Avoid passing master admin credentials in application code. Instead, issue restricted API tokens:
1. Go to **Settings > Roles & Permissions** and select **Create Role**.
2. Name the role (e.g., `Frontend Web Application`) and set explicit CRUD rules.
3. Go to **Settings > Users** and select **Create User**.
4. Assign this user to your new role.
5. In the **Token** field, enter a secure random static string and save.
6. Pass this token in your client request headers:
```bash
Authorization: Bearer <your_static_token>
```
---
## 13. Performance Tuning (Redis & Memory)
For production environments serving high traffic, fine-tune caching and container memory allocation.
### Tuning Redis Behavior
You can adjust Redis cache behavior via environment variables in `.env`:
* `CACHE_TTL="5m"`: Defines how long cached data remains stored in memory.
* `CACHE_AUTO_PURGE="true"`: Automatically invalidates related query caches whenever collection items are modified, ensuring clients always receive fresh data.
### Adjusting Node.js Memory Limits
Node.js applications running inside resource-constrained containers may encounter Out of Memory (OOM) errors during bulk asset operations. Allocate explicit heap space in `docker-compose.yml`:
```yaml
directus:
# ...
environment:
NODE_OPTIONS: "--max-old-space-size=2048" # Allocates 2GB Node heap memory
```
---
## 14. Ongoing Infrastructure Management & Maintenance
Managing a production Directus stack requires routine day-two operations like applying updates, rotating log files, and performing database maintenance.
### Updating Directus Safely
When a new Directus image release is published, follow this zero-downtime update workflow:
1. **Back up your database:** Run a `pg_dump` before updating.
2. **Pull updated images:**
```bash
docker compose pull
```
3. **Execute database migrations:**
```bash
docker compose run --rm directus database migrate:latest
```
4. **Recreate containers:**
```bash
docker compose up -d
```
5. **Verify health:** Check container logs (`docker compose logs -f directus`) and test API routes.
### Docker Container Log Rotation
By default, Docker container logs can grow indefinitely and fill up server disk space (`/var/lib/docker`). Configure daemon log rotation by creating `/etc/docker/daemon.json`:
```json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
```
Restart Docker to apply log rotation:
```bash
sudo systemctl restart docker
```
### PostgreSQL Database Maintenance
Run routine PostgreSQL index and query optimization periodically inside the database container:
```bash
docker exec -it directus_db vacuumdb -U directus_user -d directus --analyze
```
---
## 15. Troubleshooting Deployment Issues
Here are solutions to common self-hosted infrastructure issues:
### 1. Nginx 502 Bad Gateway
* **Verify container status:** Run `docker compose ps` to confirm `directus_app` is running.
* **Check local port binding:** Ensure your `docker-compose.yml` maps port `127.0.0.1:8055:8055`. If changed, update Nginx `proxy_pass`.
* **Review application logs:** Run `docker compose logs directus` to check Node process output.
### 2. Database Connection Failures
* **Check environment variables:** Confirm that `.env` DB variables (`DB_DATABASE`, `DB_USER`, `DB_PASSWORD`) match the PostgreSQL container settings (`POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`).
* **Check DB readiness:** Run `docker compose exec database pg_isready -U directus_user -d directus`.
### 3. File Upload / EACCES Permission Denied Errors
* Confirm that host directories are owned by user ID 1000:
```bash
ls -la /opt/directus
```
If directories are owned by `root`, re-apply ownership:
```bash
sudo chown -R 1000:1000 /opt/directus/uploads /opt/directus/extensions
```
### 4. Running Migrations Manually
If database migrations fail or hang during a version upgrade, run the migration command manually inside a transient container:
```bash
docker compose run --rm directus database migrate:latest
```
After migrations complete, restart the main application:
```bash
docker compose up -d
```
---
## 16. Backups, Restores, & Disaster Recovery
A production deployment requires regular automated backups of both the PostgreSQL database and uploaded files.
### 1. Automated PostgreSQL Backups
Create a backup folder on your host:
```bash
mkdir -p /opt/backups
```
Open crontab:
```bash
crontab -e
```
Add a scheduled nightly job to dump the database at 2:00 AM:
```cron
0 2 * * * docker exec directus_db pg_dump -U directus_user directus > /opt/backups/directus_$(date +\%F).sql
```
> **Note:** The backslash before `%F` (`\%F`) is required in crontab syntax to prevent cron from interpreting `%` as a newline.
#### Restoring the Database
1. Stop the Directus application container to prevent write operations:
```bash
docker compose stop directus
```
2. Restore your SQL dump:
```bash
docker exec -i directus_db psql -U directus_user -d directus < /opt/backups/directus_2026-08-15.sql
```
3. Restart Directus:
```bash
docker compose start directus
```
### 2. Upload Asset Backups
Backup media uploads stored in `/opt/directus/uploads`:
```bash
tar -czf /opt/backups/uploads_$(date +%F).tar.gz -C /opt/directus uploads
```
To restore media uploads from an archive:
```bash
tar -xzf /opt/backups/uploads_2026-08-15.tar.gz -C /opt/directus/
```
---
## 17. Don't Want to Manage Infrastructure? (Managed Option)
Self-hosting Directus requires managing server security, firewall rules, Docker Compose stacks, Let's Encrypt SSL renewals, PostgreSQL database backups, and Redis cache tuning.
If you prefer a managed option without maintaining underlying Linux servers, you can host Directus using [SiliconPin Directus Cloud Hosting](https://siliconpin.com/services/directus-cloud-hosting). Powered by rootless [SiliconPin Pods](https://siliconpin.com/siliconpin-pod), it provides managed PostgreSQL sidecars, Redis caching, automatic SSL, and instant snapshot backups (`sp backup`).
### Quick Deployment via `sp` CLI:
```bash
# 1. Install sp CLI & authenticate
curl -fsSL https://siliconpin.com/downloads/sp/install.sh | bash
sp login --token=$SP_TOKEN
# 2. Deploy Directus pod & attach PostgreSQL sidecar
sp deploy --name=<application-name> --port=8055
sp deployments attach db postgresql <deployment-id>
# 3. Configure environment variables
sp env set KEY=your_generated_key_here
sp env set SECRET=your_generated_secret_here
sp env set PUBLIC_URL=https://directus-app.yourname.sp.net
sp env set [email protected]
sp env set ADMIN_PASSWORD=ChooseAStrongAdminPassword123!
# 4. Optional: Snapshot backups & local HTTPS tunneling
sp backup <deployment-id>
sp tunnel 8055
```
---
## 18. Frequently Asked Questions
### Can I run SQLite in production instead of PostgreSQL?
While Directus supports SQLite for prototyping or local homelabs, SQLite is not suited for production deployments. It lacks concurrent write performance and table locking efficiency under load. Use PostgreSQL or MySQL for production.
### How do I install custom Directus extensions?
Place compiled extension packages inside `/opt/directus/extensions`. Because this host folder is mounted inside the container via `docker-compose.yml`, Directus scans and loads new extensions upon restart:
```bash
docker compose restart directus
```
### How do I fix CORS errors on the frontend?
CORS errors stem from misconfigured origin headers. In `.env`:
1. Verify `PUBLIC_URL` matches your domain (e.g. `https://api.yourdomain.com`).
2. Confirm `CORS_ENABLED="true"`.
3. Set `CORS_ORIGIN` to `true` for open APIs, or set it explicitly to your frontend domain (e.g. `https://yourfrontend.com`).
### How do I increase file upload payload limits?
To allow file uploads larger than 50MB, update limits in two places:
1. Increase `MAX_PAYLOAD_SIZE="100mb"` in `.env`.
2. Increase `client_max_body_size 100M;` in `/etc/nginx/sites-available/directus`.
Reload Nginx (`sudo systemctl reload nginx`) and restart Directus containers.
### Does Directus v12 support AI integrations (MCP)?
Yes. Directus v12 includes native support for Model Context Protocol (MCP). This enables AI clients (such as Claude or Cursor) to query your Directus schemas and data while strictly enforcing your defined RBAC permissions. MCP options can be managed in Project Settings.
### How do license activations work under the Open Innovation Grant?
OIG license keys bind directly to your domain (`PUBLIC_URL`). A standard OIG key allows up to **5 concurrent domain activations**. If you run ephemeral CI/CD environments that spin up temporary URLs, run staging environments on the free Core tier to avoid using up activation slots.
---
## 19. Summary
You now have a production-ready, self-hosted Directus instance running on Linux. Your installation features:
* Automated container orchestration via Docker Compose V2.
* Persistent PostgreSQL 16 database storage.
* High-performance Redis query and schema caching.
* Reverse proxying through Nginx with free Let's Encrypt SSL certificates.
* Native RBAC, 2FA, Argon2id security, and infrastructure hardening.
* Day-two maintenance workflows (container updates, log rotation, and database optimization).
* Automated nightly database and asset backup workflows.
From here, log into your Data Studio, create your database collections, set up user permissions, and start building your applications with auto-generated REST and GraphQL APIs.
Discussion
No comments yet. Be the first to start the discussion.