Skip to main content

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:

ConfigurationUse CaseTLSMonitoring
BasicDevelopment, testingNoNo
ProductionLive deploymentsYes (Caddy)Optional
FederationMulti-instance clustersYesYes

Basic Setup

Get started quickly with a minimal Docker Compose configuration.

Quick Start

  1. Create a project directory:

    mkdir nekzus && cd nekzus
  2. Create docker-compose.yml:

    docker-compose.yml
    services:
    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:
  3. Generate secure secrets:

    echo "NEKZUS_JWT_SECRET=$(openssl rand -base64 32)" > .env
    echo "NEKZUS_BOOTSTRAP_TOKEN=$(openssl rand -base64 24)" >> .env
  4. Start the service:

    docker compose up -d
  5. Verify it's running:

    curl http://localhost:8080/api/v1/healthz
    # Expected: ok
Docker Socket Access

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

d2

Production Compose File

docker-compose.yml
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

:8443 {
reverse_proxy nekzus:8080
tls internal
}

:80 {
redir https://{host}:8443{uri} permanent
}
Security Notice

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.

.env
# 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

# 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

Complete Environment Reference

VariableDescriptionRequiredDefault
NEKZUS_JWT_SECRETJWT signing secret (32+ chars)YesNone
NEKZUS_BOOTSTRAP_TOKENBootstrap authentication tokenYesNone
NEKZUS_ADDRServer listen addressNo:8080
NEKZUS_BASE_URLPublic URL for QR pairingNoAuto-detect
NEKZUS_TLS_CERTPath to TLS certificateNoNone
NEKZUS_TLS_KEYPath to TLS private keyNoNone
NEKZUS_DATABASE_PATHSQLite database pathNo./data/nexus.db
Secret Security
  • Never commit .env files to version control
  • Add .env to 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:

VolumePurposeCritical
/app/dataSQLite database, backupsYes
/app/configsConfiguration filesNo (can be bind-mounted)

Named Volumes vs 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 volume commands

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
backup.sh
#!/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

services:
nekzus:
ports:
- "8080:8080" # Exposed to host

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
Security Consideration

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
ServiceMin CPUMax CPUMin MemoryMax Memory
Nekzus0.52.0256 MB1 GB
Caddy0.251.064 MB256 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

ParameterDescriptionRecommended
intervalTime between checks30s
timeoutMax time for check5s
retriesFailures before unhealthy3
start_periodGrace period at start10s

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:

docker-compose.monitoring.yml
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:
prometheus.yml
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:

docker-compose.federation.yml
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_URL not 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