Docker Deployment

Deploy Zero using Docker Compose for quick single-server or small-scale deployments. This is the fastest way to get a self-hosted Zero instance running.

Prerequisites: Ensure you've reviewed the requirements before proceeding.

Quick Start

# Clone the repository
git clone https://zero.xaltrax.com/contact.git
cd zero

# Copy environment template
cp .env.example .env

# Edit configuration
nano .env

# Start all services
docker compose up -d

Configuration

Environment Variables

Edit the .env file with your settings:

# Domain Configuration
DOMAIN=zero.yourdomain.com
API_URL=https://api.yourdomain.com
CONSOLE_URL=https://console.yourdomain.com

# Database
POSTGRES_PASSWORD=your-secure-password-here
POSTGRES_DB=zero

# Redis
REDIS_PASSWORD=your-redis-password

# JWT Secret (generate with: openssl rand -base64 32)
JWT_SECRET=your-jwt-secret-here

# Encryption Key (generate with: openssl rand -base64 32)
ENCRYPTION_KEY=your-encryption-key-here

# VPN Configuration
VPN_ENDPOINT=vpn.yourdomain.com
VPN_PORT=51820

# Optional: SMTP for notifications
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=notifications@yourdomain.com
SMTP_PASSWORD=your-smtp-password

# Optional: SSO Configuration
OIDC_ISSUER_URL=https://auth.yourdomain.com
OIDC_CLIENT_ID=zero
OIDC_CLIENT_SECRET=your-oidc-secret

Docker Compose File

The included docker-compose.yml deploys:

  • zero-api - REST API server
  • zero-web - Management console
  • postgres - PostgreSQL 16 database
  • redis - Redis 7 cache
  • wireguard - VPN server
  • prometheus - Metrics collection (optional)
  • grafana - Dashboards (optional)

Service Configuration

version: '3.8'

services:
  api:
    image: ghcr.io/zero/zero-api:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://zero:${POSTGRES_PASSWORD}@postgres:5432/zero
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - ENCRYPTION_KEY=${ENCRYPTION_KEY}
    depends_on:
      - postgres
      - redis
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=zero
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=zero

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data

  wireguard:
    image: ghcr.io/zero/zero-vpn:latest
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    ports:
      - "51820:51820/udp"
    volumes:
      - wireguard_data:/etc/wireguard
    environment:
      - VPN_ENDPOINT=${VPN_ENDPOINT}
      - API_URL=http://api:8080

volumes:
  postgres_data:
  redis_data:
  wireguard_data:

SSL/TLS Setup

Using Let's Encrypt (Recommended)

# Install Caddy as reverse proxy
docker run -d \
  --name caddy \
  --network zero_default \
  -p 80:80 -p 443:443 \
  -v caddy_data:/data \
  -v caddy_config:/config \
  -v ./Caddyfile:/etc/caddy/Caddyfile \
  caddy:latest

Create a Caddyfile:

api.yourdomain.com {
    reverse_proxy api:8080
}

console.yourdomain.com {
    reverse_proxy web:3000
}

Using Custom Certificates

# Mount your certificates
volumes:
  - ./certs/cert.pem:/etc/ssl/certs/zero.pem
  - ./certs/key.pem:/etc/ssl/private/zero.key

Database Initialization

On first run, initialize the database:

# Run migrations
docker compose exec api zero-api migrate up

# Create admin user
docker compose exec api zero-api admin create \
  --email admin@yourdomain.com \
  --password your-secure-password

# Create organization
docker compose exec api zero-api org create \
  --name "Your Organization" \
  --admin admin@yourdomain.com

Verification

# Check all services are running
docker compose ps

# Check API health
curl https://api.yourdomain.com/api/v1/health

# Check logs
docker compose logs -f api

# Test database connection
docker compose exec postgres psql -U zero -c "SELECT version();"

Backup & Restore

Automated Backup

#!/bin/bash
# backup.sh - Run daily via cron

BACKUP_DIR=/var/backups/zero
DATE=$(date +%Y%m%d_%H%M%S)

# Backup PostgreSQL
docker compose exec -T postgres pg_dump -U zero zero | \
  gzip > $BACKUP_DIR/postgres_$DATE.sql.gz

# Backup Redis
docker compose exec -T redis redis-cli --pass $REDIS_PASSWORD BGSAVE
docker cp zero-redis-1:/data/dump.rdb $BACKUP_DIR/redis_$DATE.rdb

# Backup WireGuard keys
docker cp zero-wireguard-1:/etc/wireguard $BACKUP_DIR/wireguard_$DATE

# Cleanup old backups (keep 30 days)
find $BACKUP_DIR -mtime +30 -delete

Restore

# Restore PostgreSQL
gunzip -c backup.sql.gz | docker compose exec -T postgres psql -U zero zero

# Restore Redis
docker cp redis_backup.rdb zero-redis-1:/data/dump.rdb
docker compose restart redis

Updating

# Pull latest images
docker compose pull

# Restart with new images
docker compose up -d

# Run any new migrations
docker compose exec api zero-api migrate up

Monitoring

Enable Prometheus and Grafana for monitoring:

# In docker-compose.yml, uncomment:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana

Troubleshooting

Issue Solution
API not starting Check docker compose logs api for errors
Database connection failed Verify POSTGRES_PASSWORD matches in .env
VPN not working Ensure UDP 51820 is open in firewall
SSL errors Check certificate paths and permissions

Next Steps