Made Open

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 legacy docker-compose)

Install Docker if not already present:

Linux note: The development docker-compose.yml includes extra_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:

RecordValue
api.example.com203.0.113.10
app.example.com203.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.

  1. Create a new project at supabase.com/dashboard
  2. Install the Supabase CLI and link your project:
supabase login
supabase link --project-ref <your-project-ref>
supabase db push
  1. Copy these values from Settings > API in the Supabase dashboard:
    • SUPABASE_URL
    • SUPABASE_SERVICE_ROLE_KEY
    • SUPABASE_ANON_KEY
    • DATABASE_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:

VolumeContents
nats_dataNATS JetStream data
meili_dataMeilisearch indexes
redis_dataRedis append-only file
letsencryptTLS 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:

EndpointDescription
GET /healthLiveness probe
GET /health/detailedAll service statuses
GET /metricsPrometheus-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