Docker Deployment Guide

This guide covers deploying OpenGSLB using Docker containers for both Agent and Overwatch modes.

Image Information

Registry

Image

Tags

GitHub Container Registry

ghcr.io/loganrossus/opengslb

latest, v0.6.0, sha-*

Quick Start

Overwatch Mode

# Create configuration directory
mkdir -p ./config

# Create overwatch configuration (see Configuration section)
# Then run:
docker run -d \
  --name opengslb-overwatch \
  --restart unless-stopped \
  -p 53:53/udp \
  -p 53:53/tcp \
  -p 7946:7946/tcp \
  -p 7946:7946/udp \
  -p 9090:9090 \
  -p 9091:9091 \
  -v ./config/overwatch.yaml:/etc/opengslb/config.yaml:ro \
  -v ./data:/var/lib/opengslb \
  ghcr.io/loganrossus/opengslb:latest

Agent Mode

docker run -d \
  --name opengslb-agent \
  --restart unless-stopped \
  --network host \
  -v ./config/agent.yaml:/etc/opengslb/config.yaml:ro \
  -v ./data:/var/lib/opengslb \
  ghcr.io/loganrossus/opengslb:latest

Port Reference

Overwatch Ports

Port

Protocol

Purpose

Required

53

UDP

DNS queries (primary)

Yes

53

TCP

DNS queries (large responses, zone transfers)

Yes

7946

TCP/UDP

Gossip communication

Yes

9090

TCP

REST API

Yes

9091

TCP

Prometheus metrics

Recommended

Agent Ports

Port

Protocol

Purpose

Required

9100

TCP

Prometheus metrics

Optional

Configuration

Overwatch Configuration

# config/overwatch.yaml
mode: overwatch

overwatch:
  identity:
    node_id: overwatch-docker-1
    region: us-east

  agent_tokens:
    myapp: "secret-token-for-myapp"

  gossip:
    bind_address: "0.0.0.0:7946"
    encryption_key: "YOUR_BASE64_GOSSIP_KEY_HERE"

  validation:
    enabled: true
    check_interval: 30s
    check_timeout: 5s

  stale:
    threshold: 30s
    remove_after: 5m

  dnssec:
    enabled: true

  data_dir: /var/lib/opengslb

dns:
  listen_address: "0.0.0.0:53"
  default_ttl: 30
  zones:
    - gslb.example.com

regions:
  - name: us-east
    servers: []

domains:
  - name: myapp.gslb.example.com
    routing_algorithm: round-robin
    regions:
      - us-east

logging:
  level: info
  format: json

metrics:
  enabled: true
  address: ":9091"

api:
  enabled: true
  address: ":9090"
  allowed_networks:
    - 0.0.0.0/0  # Allow all (restrict in production)

Agent Configuration

# config/agent.yaml
mode: agent

agent:
  identity:
    service_token: "secret-token-for-myapp"
    region: us-east

  backends:
    - service: myapp
      address: host.docker.internal  # or actual backend IP
      port: 8080
      weight: 100
      health_check:
        type: http
        path: /health
        interval: 5s
        timeout: 2s
        failure_threshold: 3
        success_threshold: 2

  gossip:
    encryption_key: "YOUR_BASE64_GOSSIP_KEY_HERE"
    overwatch_nodes:
      - overwatch:7946

  heartbeat:
    interval: 10s

logging:
  level: info
  format: json

metrics:
  enabled: true
  address: ":9100"

Docker Compose

Development Environment

# docker-compose.yml
version: '3.8'

services:
  overwatch:
    image: ghcr.io/loganrossus/opengslb:latest
    container_name: opengslb-overwatch
    ports:
      - "53:53/udp"
      - "53:53/tcp"
      - "7946:7946/tcp"
      - "7946:7946/udp"
      - "9090:9090"
      - "9091:9091"
    volumes:
      - ./config/overwatch.yaml:/etc/opengslb/config.yaml:ro
      - overwatch-data:/var/lib/opengslb
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/api/v1/live"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  agent:
    image: ghcr.io/loganrossus/opengslb:latest
    container_name: opengslb-agent
    network_mode: host
    volumes:
      - ./config/agent.yaml:/etc/opengslb/config.yaml:ro
      - agent-data:/var/lib/opengslb
    restart: unless-stopped
    depends_on:
      - overwatch

  # Example backend service
  backend:
    image: nginx:alpine
    container_name: backend
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 10s
      timeout: 5s

volumes:
  overwatch-data:
  agent-data:

Production Environment (HA)

# docker-compose.ha.yml
version: '3.8'

services:
  overwatch-1:
    image: ghcr.io/loganrossus/opengslb:latest
    container_name: opengslb-overwatch-1
    hostname: overwatch-1
    ports:
      - "10.0.1.53:53:53/udp"
      - "10.0.1.53:53:53/tcp"
      - "10.0.1.53:7946:7946/tcp"
      - "10.0.1.53:7946:7946/udp"
      - "10.0.1.53:9090:9090"
      - "10.0.1.53:9091:9091"
    volumes:
      - ./config/overwatch-1.yaml:/etc/opengslb/config.yaml:ro
      - ./geoip:/var/lib/opengslb/geoip:ro
      - overwatch-1-data:/var/lib/opengslb
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"

  overwatch-2:
    image: ghcr.io/loganrossus/opengslb:latest
    container_name: opengslb-overwatch-2
    hostname: overwatch-2
    ports:
      - "10.0.1.54:53:53/udp"
      - "10.0.1.54:53:53/tcp"
      - "10.0.1.54:7946:7946/tcp"
      - "10.0.1.54:7946:7946/udp"
      - "10.0.1.54:9090:9090"
      - "10.0.1.54:9091:9091"
    volumes:
      - ./config/overwatch-2.yaml:/etc/opengslb/config.yaml:ro
      - ./geoip:/var/lib/opengslb/geoip:ro
      - overwatch-2-data:/var/lib/opengslb
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

  overwatch-3:
    image: ghcr.io/loganrossus/opengslb:latest
    container_name: opengslb-overwatch-3
    hostname: overwatch-3
    ports:
      - "10.0.1.55:53:53/udp"
      - "10.0.1.55:53:53/tcp"
      - "10.0.1.55:7946:7946/tcp"
      - "10.0.1.55:7946:7946/udp"
      - "10.0.1.55:9090:9090"
      - "10.0.1.55:9091:9091"
    volumes:
      - ./config/overwatch-3.yaml:/etc/opengslb/config.yaml:ro
      - ./geoip:/var/lib/opengslb/geoip:ro
      - overwatch-3-data:/var/lib/opengslb
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

volumes:
  overwatch-1-data:
  overwatch-2-data:
  overwatch-3-data:

With Monitoring Stack

# docker-compose.monitoring.yml
version: '3.8'

services:
  overwatch:
    image: ghcr.io/loganrossus/opengslb:latest
    ports:
      - "53:53/udp"
      - "53:53/tcp"
      - "7946:7946"
      - "9090:9090"
      - "9091:9091"
    volumes:
      - ./config/overwatch.yaml:/etc/opengslb/config.yaml:ro
      - overwatch-data:/var/lib/opengslb
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9092:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped
    depends_on:
      - prometheus

volumes:
  overwatch-data:
  prometheus-data:
  grafana-data:

Prometheus configuration:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'opengslb-overwatch'
    static_configs:
      - targets: ['overwatch:9091']

  - job_name: 'opengslb-agent'
    static_configs:
      - targets: ['agent:9100']

Networking

Bridge Network (Default)

With bridge networking, containers can reach external IPs but not the host directly.

# For backend on external network
backends:
  - service: myapp
    address: 10.0.1.100  # External IP works
    port: 8080

Host Network Mode

For agents that need to reach local backends:

docker run -d \
  --name opengslb-agent \
  --network host \
  -v ./config/agent.yaml:/etc/opengslb/config.yaml:ro \
  ghcr.io/loganrossus/opengslb:latest

Accessing Host Services

Docker Desktop (Mac/Windows):

backends:
  - service: myapp
    address: host.docker.internal
    port: 8080

Linux:

# Get host IP
docker network inspect bridge | grep Gateway
# Use that IP in configuration

Volume Management

Data Persistence

# Create named volumes
docker volume create opengslb-overwatch-data
docker volume create opengslb-agent-data

# Run with named volumes
docker run -d \
  -v opengslb-overwatch-data:/var/lib/opengslb \
  ...

Backup Volumes

# Backup volume to tar
docker run --rm \
  -v opengslb-overwatch-data:/data:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/overwatch-backup.tar.gz -C /data .

# Restore from tar
docker run --rm \
  -v opengslb-overwatch-data:/data \
  -v $(pwd):/backup:ro \
  alpine tar xzf /backup/overwatch-backup.tar.gz -C /data

Security Considerations

Read-Only Root Filesystem

services:
  overwatch:
    image: ghcr.io/loganrossus/opengslb:latest
    read_only: true
    tmpfs:
      - /tmp
    volumes:
      - ./config/overwatch.yaml:/etc/opengslb/config.yaml:ro
      - overwatch-data:/var/lib/opengslb

Non-Root User

The container runs as non-root by default. If you need to verify:

docker exec opengslb-overwatch id
# Should show: uid=65532(nonroot) gid=65532(nonroot)

Secret Management

For production, use Docker secrets or environment variable injection:

services:
  overwatch:
    image: ghcr.io/loganrossus/opengslb:latest
    environment:
      - GOSSIP_KEY_FILE=/run/secrets/gossip_key
    secrets:
      - gossip_key

secrets:
  gossip_key:
    external: true

Health Checks

services:
  overwatch:
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/api/v1/live"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

Or using DNS:

healthcheck:
  test: ["CMD", "dig", "@localhost", "configured-domain.example.com", "+short", "+time=2"]
  interval: 30s
  timeout: 10s
  retries: 3

Logging

View Logs

# Follow logs
docker logs -f opengslb-overwatch

# Last 100 lines
docker logs --tail 100 opengslb-overwatch

# Since timestamp
docker logs --since 2025-01-01T00:00:00 opengslb-overwatch

Log Rotation

services:
  overwatch:
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"

External Logging

services:
  overwatch:
    logging:
      driver: "syslog"
      options:
        syslog-address: "tcp://logserver:514"
        tag: "opengslb-overwatch"

Upgrading

Rolling Upgrade with Compose

# Pull new image
docker-compose pull

# Recreate containers one at a time
docker-compose up -d --no-deps overwatch-1
sleep 30
docker-compose up -d --no-deps overwatch-2
sleep 30
docker-compose up -d --no-deps overwatch-3

Blue-Green Deployment

# Start new version
docker run -d --name opengslb-overwatch-new \
  -p 5353:53/udp \
  ...

# Test new version
dig @localhost -p 5353 myapp.gslb.example.com

# Switch traffic
docker stop opengslb-overwatch
docker rename opengslb-overwatch opengslb-overwatch-old
docker rename opengslb-overwatch-new opengslb-overwatch
docker start opengslb-overwatch

# Cleanup
docker rm opengslb-overwatch-old

Troubleshooting

Port 53 Conflict

# Check what's using port 53
sudo lsof -i :53

# Common culprit: systemd-resolved
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved

# Or change DNS stub listener
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved

Container Won’t Start

# Check logs
docker logs opengslb-overwatch

# Common issues:
# - Config file not mounted correctly
# - Invalid YAML syntax
# - Port already in use
# - Permission denied on data volume

Cannot Reach Backends

# Enter container and test
docker exec -it opengslb-overwatch sh
wget -O- http://10.0.1.100:8080/health

# Check DNS resolution
docker exec -it opengslb-overwatch nslookup backend

Building Custom Image

# Clone repository
git clone https://github.com/loganrossus/OpenGSLB.git
cd OpenGSLB

# Build image
docker build -t opengslb:custom .

# Run custom image
docker run -d --name opengslb opengslb:custom