Docker Compose Deployment
This guide covers deploying Nekzus using Docker Compose, from basic development setups to production-ready configurations with TLS, monitoring, and resource management.
Overview
Docker Compose simplifies multi-container deployments by defining services, networks, and volumes in a single YAML file. Nekzus provides several Compose configurations:
| Configuration | Use Case | TLS | Monitoring |
|---|---|---|---|
| Basic | Development, testing | No | No |
| Production | Live deployments | Yes (Caddy) | Optional |
| Federation | Multi-instance clusters | Yes | Yes |
Basic Setup
Get started quickly with a minimal Docker Compose configuration.
Quick Start
-
Create a project directory:
mkdir nekzus && cd nekzus -
Create
docker-compose.yml:docker-compose.ymlservices:
nekzus:
image: nstalgic/nekzus:latest
container_name: nekzus
ports:
- "8080:8080"
environment:
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET:-change-this-to-strong-secret-min-32-chars}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN:-change-this-bootstrap-token}"
NEKZUS_ADDR: ":8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- nekzus-data:/app/data
restart: unless-stopped
healthcheck:
test: ["/app/nekzus", "--health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes:
nekzus-data: -
Generate secure secrets:
echo "NEKZUS_JWT_SECRET=$(openssl rand -base64 32)" > .env
echo "NEKZUS_BOOTSTRAP_TOKEN=$(openssl rand -base64 24)" >> .env -
Start the service:
docker compose up -d -
Verify it's running:
curl http://localhost:8080/api/v1/healthz
# Expected: ok
Mounting the Docker socket (/var/run/docker.sock) enables automatic container discovery. If you don't need this feature, you can remove this volume mount for improved security.
Production Setup with TLS
For production deployments, use Caddy as a reverse proxy to handle TLS termination. This approach separates concerns and provides automatic HTTPS.
Architecture
Production Compose File
services:
nekzus:
image: nstalgic/nekzus:latest
container_name: nekzus
networks:
- nekzus
environment:
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN}"
NEKZUS_BASE_URL: "${NEKZUS_BASE_URL:-https://localhost:8443}"
NEKZUS_ADDR: ":8080"
command: ["--config", "/app/configs/config.yaml", "--insecure-http"]
volumes:
- ./config.yaml:/app/configs/config.yaml:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- nekzus-data:/app/data
restart: unless-stopped
healthcheck:
test: ["/app/nekzus", "--health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
caddy:
image: caddy:2.8-alpine
container_name: nekzus-caddy
depends_on:
nekzus:
condition: service_started
networks:
- nekzus
environment:
- NEKZUS_HOST=nekzus
ports:
- "8443:8443" # HTTPS
- "80:80" # HTTP (redirects to HTTPS)
volumes:
- ./deployments/Caddyfile:/etc/caddy/Caddyfile:ro
- ./deployments/tls:/tls:ro
- caddy-data:/data
- caddy-config:/config
restart: unless-stopped
healthcheck:
test: ["CMD", "caddy", "validate", "--config", "/etc/caddy/Caddyfile"]
interval: 30s
timeout: 5s
retries: 3
deploy:
resources:
limits:
cpus: '1.0'
memory: 256M
reservations:
cpus: '0.25'
memory: 64M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
nekzus:
driver: bridge
name: nekzus-network
volumes:
nekzus-data:
name: nekzus-data
caddy-data:
name: nekzus-caddy-data
caddy-config:
name: nekzus-caddy-config
Caddyfile Configuration
- Self-Signed (Local Network)
- Let's Encrypt (Public Domain)
- Custom Certificates
:8443 {
reverse_proxy nekzus:8080
tls internal
}
:80 {
redir https://{host}:8443{uri} permanent
}
nekzus.example.com {
reverse_proxy nekzus:8080
# Automatic Let's Encrypt certificates
}
http://nekzus.example.com {
redir https://{host}{uri} permanent
}
:8443 {
reverse_proxy nekzus:8080
tls /certs/cert.pem /certs/key.pem
}
:80 {
redir https://{host}:8443{uri} permanent
}
The --insecure-http flag is safe when Nekzus is behind a TLS-terminating reverse proxy like Caddy. Never expose an --insecure-http instance directly to the internet.
Environment Variables
Using .env Files
Docker Compose automatically loads variables from a .env file in the same directory as docker-compose.yml.
# Security (REQUIRED - generate strong random values)
NEKZUS_JWT_SECRET=your-secure-jwt-secret-minimum-32-characters
NEKZUS_BOOTSTRAP_TOKEN=your-secure-bootstrap-token-here
# Server Configuration
NEKZUS_ADDR=:8080
NEKZUS_BASE_URL=https://192.168.1.100:8443
# Optional: Database path
NEKZUS_DATABASE_PATH=/app/data/nexus.db
Generating Secure Secrets
- OpenSSL
- Python
- Manual
# Generate JWT secret (32+ characters)
echo "NEKZUS_JWT_SECRET=$(openssl rand -base64 32)" > .env
# Generate bootstrap token
echo "NEKZUS_BOOTSTRAP_TOKEN=$(openssl rand -base64 24)" >> .env
# Generate JWT secret
python3 -c "import secrets; print(f'NEKZUS_JWT_SECRET={secrets.token_urlsafe(32)}')" > .env
# Generate bootstrap token
python3 -c "import secrets; print(f'NEKZUS_BOOTSTRAP_TOKEN={secrets.token_urlsafe(24)}')" >> .env
# Create .env manually
cat > .env << 'EOF'
NEKZUS_JWT_SECRET=replace-with-strong-32-char-secret-value
NEKZUS_BOOTSTRAP_TOKEN=replace-with-strong-bootstrap-token
NEKZUS_BASE_URL=https://your-server-ip:8443
EOF
Complete Environment Reference
| Variable | Description | Required | Default |
|---|---|---|---|
NEKZUS_JWT_SECRET | JWT signing secret (32+ chars) | Yes | None |
NEKZUS_BOOTSTRAP_TOKEN | Bootstrap authentication token | Yes | None |
NEKZUS_ADDR | Server listen address | No | :8080 |
NEKZUS_BASE_URL | Public URL for QR pairing | No | Auto-detect |
NEKZUS_TLS_CERT | Path to TLS certificate | No | None |
NEKZUS_TLS_KEY | Path to TLS private key | No | None |
NEKZUS_DATABASE_PATH | SQLite database path | No | ./data/nexus.db |
- Never commit
.envfiles to version control - Add
.envto your.gitignore - Use strong, randomly generated secrets
- Rotate secrets periodically in production
Volume Management
Data Persistence
Nekzus stores data in SQLite and requires persistent volumes for:
| Volume | Purpose | Critical |
|---|---|---|
/app/data | SQLite database, backups | Yes |
/app/configs | Configuration files | No (can be bind-mounted) |
Named Volumes vs Bind Mounts
- Named Volumes (Recommended)
- Bind Mounts
services:
nekzus:
volumes:
- nekzus-data:/app/data
volumes:
nekzus-data:
name: nekzus-data
Advantages:
- Docker manages storage location
- Works across different hosts
- Easy backup with
docker volumecommands
services:
nekzus:
volumes:
- ./data:/app/data
- ./config.yaml:/app/configs/config.yaml:ro
Advantages:
- Direct filesystem access
- Easy to inspect and edit
- Simpler backup with standard tools
Backup Strategies
Automated Backups with Docker Volume
# Create backup of named volume
docker run --rm \
-v nekzus-data:/source:ro \
-v $(pwd)/backups:/backup \
alpine tar czf /backup/nekzus-$(date +%Y%m%d).tar.gz -C /source .
# Restore from backup
docker run --rm \
-v nekzus-data:/target \
-v $(pwd)/backups:/backup:ro \
alpine tar xzf /backup/nekzus-20240101.tar.gz -C /target
Built-in Backup Configuration
Nekzus includes automatic database backups. Configure in config.yaml:
backup:
enabled: true
directory: "./data/backups"
schedule: "24h"
retention: 7
Bind Mount Backup Script
#!/bin/bash
BACKUP_DIR="./backups"
DATA_DIR="./data"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Stop for consistent backup (optional)
docker compose stop nekzus
# Create backup
tar czf "$BACKUP_DIR/nekzus-$DATE.tar.gz" -C "$DATA_DIR" .
# Restart
docker compose start nekzus
# Keep only last 7 backups
ls -t "$BACKUP_DIR"/nekzus-*.tar.gz | tail -n +8 | xargs rm -f 2>/dev/null
echo "Backup created: $BACKUP_DIR/nekzus-$DATE.tar.gz"
Networking
Bridge Networks
Docker Compose creates a default bridge network for inter-container communication. For production, define explicit networks:
services:
nekzus:
networks:
- frontend
- backend
caddy:
networks:
- frontend
database:
networks:
- backend
networks:
frontend:
driver: bridge
name: nekzus-frontend
backend:
driver: bridge
name: nekzus-backend
internal: true # No external access
Port Exposure
- Development
- Production (Behind Proxy)
- Specific Interface
services:
nekzus:
ports:
- "8080:8080" # Exposed to host
services:
nekzus:
expose:
- "8080" # Only accessible within Docker network
networks:
- internal
caddy:
ports:
- "443:443"
- "80:80"
networks:
- internal
services:
nekzus:
ports:
- "127.0.0.1:8080:8080" # Only localhost
- "192.168.1.100:8080:8080" # Specific IP
Docker Socket for Discovery
To enable automatic container discovery, mount the Docker socket:
services:
nekzus:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
The Docker socket provides root-level access to the host. Mount it read-only (:ro) and consider using a Docker socket proxy for additional security in high-security environments.
Resource Limits
CPU and Memory Constraints
Define resource limits to prevent runaway containers:
services:
nekzus:
deploy:
resources:
limits:
cpus: '2.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
Recommended Resources
| Service | Min CPU | Max CPU | Min Memory | Max Memory |
|---|---|---|---|---|
| Nekzus | 0.5 | 2.0 | 256 MB | 1 GB |
| Caddy | 0.25 | 1.0 | 64 MB | 256 MB |
Logging Configuration
Prevent disk exhaustion with log rotation:
services:
nekzus:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Health Checks
Container Health Monitoring
Nekzus includes a built-in health check endpoint:
services:
nekzus:
healthcheck:
test: ["/app/nekzus", "--health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
Health Check Options
| Parameter | Description | Recommended |
|---|---|---|
interval | Time between checks | 30s |
timeout | Max time for check | 5s |
retries | Failures before unhealthy | 3 |
start_period | Grace period at start | 10s |
Dependency Health Checks
Wait for dependencies to be healthy before starting:
services:
nekzus:
depends_on:
database:
condition: service_healthy
caddy:
depends_on:
nekzus:
condition: service_healthy
Monitoring Health Status
# Check container health
docker compose ps
# Detailed health status
docker inspect nekzus --format='{{.State.Health.Status}}'
# Health check logs
docker inspect nekzus --format='{{json .State.Health.Log}}' | jq
Multi-Service Examples
Nekzus with Monitoring Stack
Deploy Nekzus with Prometheus and Grafana for observability:
services:
nekzus:
image: nstalgic/nekzus:latest
container_name: nekzus
networks:
- nekzus
- monitoring
environment:
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN}"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- nekzus-data:/app/data
restart: unless-stopped
healthcheck:
test: ["/app/nekzus", "--health"]
interval: 30s
timeout: 5s
retries: 3
prometheus:
image: prom/prometheus:latest
container_name: nekzus-prometheus
networks:
- monitoring
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: nekzus-grafana
networks:
- monitoring
environment:
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD:-admin}"
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
restart: unless-stopped
networks:
nekzus:
driver: bridge
monitoring:
driver: bridge
volumes:
nekzus-data:
prometheus-data:
grafana-data:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'nekzus'
static_configs:
- targets: ['nekzus:8080']
metrics_path: /metrics
Federation Cluster
Deploy multiple Nekzus instances for high availability and distributed service discovery:
services:
nekzus-1:
image: nstalgic/nekzus:latest
container_name: nekzus-1
hostname: nekzus-1
networks:
- federation
ports:
- "8080:8080"
- "7946:7946/tcp"
- "7946:7946/udp"
environment:
NEKZUS_ID: "nekzus-instance-1"
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN}"
NEKZUS_FEDERATION_ENABLED: "true"
NEKZUS_CLUSTER_SECRET: "${NEKZUS_CLUSTER_SECRET}"
NEKZUS_GOSSIP_PORT: "7946"
volumes:
- ./configs/instance1.yaml:/app/configs/config.yaml:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- federation-data-1:/app/data
restart: unless-stopped
nekzus-2:
image: nstalgic/nekzus:latest
container_name: nekzus-2
hostname: nekzus-2
networks:
- federation
ports:
- "8081:8080"
- "7947:7946/tcp"
- "7947:7946/udp"
environment:
NEKZUS_ID: "nekzus-instance-2"
NEKZUS_JWT_SECRET: "${NEKZUS_JWT_SECRET}"
NEKZUS_BOOTSTRAP_TOKEN: "${NEKZUS_BOOTSTRAP_TOKEN}"
NEKZUS_FEDERATION_ENABLED: "true"
NEKZUS_CLUSTER_SECRET: "${NEKZUS_CLUSTER_SECRET}"
NEKZUS_GOSSIP_PORT: "7946"
volumes:
- ./configs/instance2.yaml:/app/configs/config.yaml:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- federation-data-2:/app/data
depends_on:
- nekzus-1
restart: unless-stopped
networks:
federation:
driver: bridge
name: nekzus-federation
ipam:
config:
- subnet: 172.30.0.0/16
volumes:
federation-data-1:
federation-data-2:
Troubleshooting
Common Issues
Container fails to start
Check the logs:
docker compose logs nekzus
Common causes:
- Port already in use:
Error: listen tcp :8080: bind: address already in use - Invalid environment variables
- Missing required secrets
- Permission issues with mounted volumes
Solutions:
# Check if port is in use
lsof -i :8080
# Verify environment variables
docker compose config
# Check volume permissions
ls -la ./data
Docker discovery not working
Verify Docker socket is mounted:
docker compose exec nekzus ls -la /var/run/docker.sock
Check discovery status:
curl http://localhost:8080/api/v1/discovery/status
Common causes:
- Docker socket not mounted or wrong path
- Permission denied on Docker socket
- Discovery disabled in configuration
Solutions:
# Ensure socket is mounted read-only
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
On some systems (especially rootless Docker), you may need additional configuration.
Health check failing
Check health status:
docker inspect nekzus --format='{{json .State.Health}}' | jq
View health check logs:
docker inspect nekzus --format='{{range .State.Health.Log}}{{.Output}}{{end}}'
Common causes:
- Container still starting (check
start_period) - Application crashed internally
- Health endpoint path changed
Cannot connect from other devices
Verify port binding:
# Check if bound to all interfaces
docker compose ps
netstat -tlnp | grep 8080
Common causes:
- Port bound to localhost only (
127.0.0.1:8080:8080) - Firewall blocking connections
NEKZUS_BASE_URLnot set for QR pairing
Solutions:
ports:
- "8080:8080" # Bind to all interfaces
# Allow through firewall (Linux)
sudo ufw allow 8080/tcp
# macOS
# Check System Preferences > Security & Privacy > Firewall
Caddy TLS certificate issues
Check Caddy logs:
docker compose logs caddy
Common causes:
- DNS not resolving for Let's Encrypt
- Port 80/443 blocked by firewall
- Rate limited by Let's Encrypt
Solutions:
# Use internal certificates for local network
:8443 {
reverse_proxy nekzus:8080
tls internal
}
Volume permission denied
Check current permissions:
ls -la ./data
docker compose exec nekzus ls -la /app/data
Fix permissions:
# For bind mounts
sudo chown -R $(id -u):$(id -g) ./data
# Or allow container user
sudo chown -R 0:0 ./data
Debugging Commands
# View real-time logs
docker compose logs -f
# Execute shell in container
docker compose exec nekzus sh
# Check container resource usage
docker stats nekzus
# Inspect container configuration
docker inspect nekzus
# View network configuration
docker network inspect nekzus-network
# List all volumes
docker volume ls | grep nekzus
# Restart specific service
docker compose restart nekzus
# Rebuild and restart
docker compose up -d --build nekzus
# Clean restart (removes volumes)
docker compose down -v && docker compose up -d
Log Analysis
# Filter logs by severity
docker compose logs nekzus 2>&1 | grep -i error
# Follow logs with timestamps
docker compose logs -f -t nekzus
# Last 100 lines
docker compose logs --tail 100 nekzus
# Export logs to file
docker compose logs nekzus > nekzus.log 2>&1
Next Steps
- Configuration Reference - Detailed configuration options
- Toolbox Guide - One-click service deployment
- API Reference - REST API documentation