Self-hosting Directus with Docker on a Ubuntu Server

Directus is an open-source content management system (CMS) that lets you build databases and APIs without coding. Hosting it yourself on an Ubuntu Linux server gives you control over costs, security, and your data.
Follow these clear steps to deploy Directus using Docker, Docker Compose, and Caddy web server for automatic HTTPS.
Before starting, you need:
- An Ubuntu Linux server with SSH access and a public IP address. Providers like Hetzner offer affordable options.
- A registered domain name pointing to your server IP.
- Basic command-line and SSH familiarity.
Step 1: Server Update
First, update your Ubuntu server packages to include the latest security patches.
sudo apt-get update
sudo apt-get upgrade -y
Wait for completion before moving on.
Step 2: Configure Firewall (UFW)
On a public server, only allow necessary ports:
- SSH: Port
22
- HTTP: Port
80
- HTTPS: Port
443
Run these commands:
sudo apt install ufw -y
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
Check your firewall setup:
sudo ufw status verbose
Be aware Docker might bypass UFW. To fix this, see this guide: Docker & UFW.
Step 3: Install Docker
Docker lets you easily deploy Directus. Install Docker and Docker Compose plugin by typing:
Set dependencies and Docker's official key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
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
Add Docker repository:
echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Install Docker engine and compose plugin:
sudo apt-get install docker-ce docker-ce-cli \
containerd.io docker-buildx-plugin docker-compose-plugin -y
Check installation:
sudo docker run hello-world
After verification, Docker is good to go.
Step 4: Setup Caddy for Automatic HTTPS
Caddy is a web server that automatically generates SSL certificates from Let's Encrypt.
Install Caddy web server:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy -y
Edit Caddy configuration file:
sudo nano /etc/caddy/Caddyfile
Replace yourdomain.com
with your domain name. Caddy passes web requests to Directus running at port 8055:
yourdomain.com {
reverse_proxy localhost:8055
}
Restart Caddy to load the new configuration:
sudo systemctl restart caddy
Step 5: Deploy Directus using Docker Compose
Docker Compose makes it convenient to manage Directus and its database. First, create a directory for Directus files:
mkdir ~/directus
cd ~/directus
Create a new file named docker-compose.yml
with the following content:
services:
database:
image: postgis/postgis:13-master
volumes:
- ./data/database:/var/lib/postgresql/data
environment:
POSTGRES_USER: "directus"
POSTGRES_PASSWORD: "directus"
POSTGRES_DB: "directus"
healthcheck:
test: ["CMD", "pg_isready", "--host=localhost", "--username=directus"]
interval: 10s
timeout: 5s
retries: 5
start_interval: 5s
start_period: 30s
cache:
image: redis:6
healthcheck:
test: ["CMD-SHELL", "[ $$(redis-cli ping) = 'PONG' ]"]
interval: 10s
timeout: 5s
retries: 5
start_interval: 5s
start_period: 30s
directus:
image: directus/directus:11.5.1
ports:
- 8055:8055
volumes:
- ./uploads:/directus/uploads
- ./extensions:/directus/extensions
depends_on:
database:
condition: service_healthy
cache:
condition: service_healthy
environment:
SECRET: "replace-with-secure-random-value"
DB_CLIENT: "pg"
DB_HOST: "database"
DB_PORT: "5432"
DB_DATABASE: "directus"
DB_USER: "directus"
DB_PASSWORD: "directus"
CACHE_ENABLED: "true"
CACHE_AUTO_PURGE: "true"
CACHE_STORE: "redis"
REDIS: "redis://cache:6379"
ADMIN_EMAIL: "[email protected]"
ADMIN_PASSWORD: "d1r3ctu5"
PUBLIC_URL: "https://yourdomain.com"
Start Directus containers by running:
sudo docker compose up -d
Docker Compose now pulls images and starts Directus running in the background at port 8055. Make sure to replace your secrets and your domain name.
Step 6: Access Your Self-hosted Directus
Open your domain https://yourdomain.com
in any browser to load Directus dashboard. Log in with the ADMIN_EMAIL
and ADMIN_PASSWORD
specified in your docker-compose file.
Security Recommendations
Keep your server secure:
- Regularly update your server and Docker images.
- Use strong passwords and restrict user access.
- Watch server logs to spot suspicious activity.
- Use Fail2ban to prevent unauthorized access.
Updating your Directus Installation
To update Directus, perform these commands from your project folder:
sudo docker compose pull
sudo docker compose up -d
Docker updates your images and automatically replaces your Directus instance.
Pricing Comparison with Hosted Providers
Below you'll see approximate monthly costs of hosted providers compared to self-hosting:
Provider | vCPU | RAM | Disk | Monthly Cost |
---|---|---|---|---|
Render.com | 1 | 2 GB | 40 GB | ~$35 |
Fly.io | 2 | 2 GB | 40 GB | ~$17–20 |
Railway | 2 | 2 GB | 40 GB | ~$15–30 |
Sliplane.io | 2 | 2 GB | 40 GB | ~€9.50 flat |
Hetzner Cloud (self-hosted) | 2 | 2 GB | 40 GB | ~€5–10/month |
By deploying Directus yourself, you cut down hosting costs and directly manage infrastructure. This approach has the trade-off of managing backups and your server on your own.
Differences from Managed Hosting
A self-hosted Directus instance is powerful and flexible. Official managed Directus hosting takes away infrastructure management from you, though typically at higher costs.
View this video if self-hosting Directus is complicated, or you'd prefer managed deployment on a simpler platform like sliplane!