Docker Compose Production Deployment
Self-hosted single-server deployment using Docker Compose and Traefik for automatic TLS.
1. Server Requirements
- OS: Ubuntu 22.04+ (or any Linux with Docker support)
- RAM: 4 GB minimum, 8 GB recommended
- Disk: 20 GB+ (depends on data volume)
- Docker: Docker Engine 24+ with Compose V2 (
docker compose— not the legacydocker-compose)
Install Docker if not already present:
Linux note: The development
docker-compose.ymlincludesextra_hosts: ["host.docker.internal:host-gateway"]for Linux compatibility. The production compose file handles this automatically.
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for group membership to take effect
2. DNS Setup
Create two DNS A records pointing to your server's public IP address:
| Record | Value |
|---|---|
api.example.com | 203.0.113.10 |
app.example.com | 203.0.113.10 |
Replace with your actual domain and IP. Traefik uses these hostnames to route traffic and provision TLS certificates via Let's Encrypt.
3. Supabase Cloud Setup
Made Open uses Supabase Cloud for PostgreSQL, Auth, and Storage in production.
- Create a new project at supabase.com/dashboard
- Install the Supabase CLI and link your project:
supabase login
supabase link --project-ref <your-project-ref>
supabase db push
- Copy these values from Settings > API in the Supabase dashboard:
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYSUPABASE_ANON_KEYDATABASE_URL(from Settings > Database)
See deployment.md for the full environment variable reference.
4. Configuration
Clone the repository and create your .env file:
git clone https://github.com/drdropout/made-open.git
cd made-open
cp .env.prod.example .env
Edit .env and fill in all values:
# Required — your domains
HUB_DOMAIN=api.example.com
WEB_DOMAIN=app.example.com
ACME_EMAIL=admin@example.com
# Required — Supabase keys from step 3
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...
SUPABASE_ANON_KEY=eyJ...
DATABASE_URL=postgresql://postgres:...
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
# Required — generate strong random secrets
JWT_SECRET=$(openssl rand -hex 32)
MEILI_MASTER_KEY=$(openssl rand -hex 16)
CORS_ORIGINS=https://app.example.com
Generate secrets with:
openssl rand -hex 32 # For JWT_SECRET
openssl rand -hex 16 # For MEILI_MASTER_KEY
5. Deploy
Pull images and start all services:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
Watch logs during first startup:
docker compose -f docker-compose.prod.yml logs -f
Traefik will automatically obtain TLS certificates from Let's Encrypt on first request. This may take 30-60 seconds.
6. Verify
Check that all services are running:
docker compose -f docker-compose.prod.yml ps
Test the health endpoints:
# Hub health
curl https://api.example.com/health
# Detailed health (all service statuses)
curl https://api.example.com/health/detailed
The /health/detailed endpoint returns real probe results for each infrastructure service (NATS, Meilisearch, database). If any service shows disconnected, check its container status with docker compose -f docker-compose.prod.yml ps.
# Web app
curl -I https://app.example.com
Open https://app.example.com in a browser to confirm the web UI loads.
7. Updates
Pull the latest images and restart:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
To deploy a specific version:
TAG=v1.2.0 docker compose -f docker-compose.prod.yml up -d
8. Backup
All persistent data is stored in Docker named volumes:
| Volume | Contents |
|---|---|
nats_data | NATS JetStream data |
meili_data | Meilisearch indexes |
redis_data | Redis append-only file |
letsencrypt | TLS certificates |
Your primary data (users, entities, messages, etc.) lives in Supabase Cloud, which handles its own backups. For the Docker volumes, use standard Docker volume backup procedures or your host's snapshot/backup tooling.
9. Monitoring
The hub exposes several monitoring endpoints:
| Endpoint | Description |
|---|---|
GET /health | Liveness probe |
GET /health/detailed | All service statuses |
GET /metrics | Prometheus-compatible metrics |
The hub outputs structured JSON logs (via pino) to stdout. Route container logs to your preferred aggregator (Datadog, Loki, CloudWatch, etc.):
# View live logs
docker compose -f docker-compose.prod.yml logs -f hub
# Export to file
docker compose -f docker-compose.prod.yml logs hub > hub.log
Further Reading
- Deployment Guide -- Full environment variable reference and platform-specific deployment options
- Getting Started -- Local development setup
- Troubleshooting -- Common issues and fixes