Files
docs/history/traefik-migration-docker-labels.md
T

8.8 KiB

STATUS: ABANDONED 2026-05-16 — Zoraxy is the production reverse proxy. Kept for design-decision history.


Traefik Migration Guide Using Docker Labels

Overview

Migrate from Zoraxy reverse proxy to Traefik using Docker labels for zero-touch service discovery.


Phase 1: Install Traefik

Step 1: Create Directory Structure

mkdir -p /opt/stacks/proxy/traefik/{config,dynamic,letsencrypt}

Step 2: Create docker-compose.yml

version: "3.8"

services:
  traefik:
    image: traefik:v3.2
    container_name: traefik
    restart: always
    network_mode: host
    security_opt:
      - no-new-privileges=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /opt/stacks/proxy/traefik/config:/etc/traefik
      - /opt/stacks/proxy/traefik/dynamic:/etc/traefik/dynamic
      - /opt/stacks/proxy/traefik/letsencrypt:/etc/letsencrypt
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=ai-internal"
      - "--providers.docker.network=shared_backend"
      - "--providers.docker.defaultRule=Host(`{{ .Name }}.nuclide.systems`)"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpChallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@nuclide.systems"
      - "--certificatesresolvers.letsencrypt.acme.storage=/etc/letsencrypt/acme.json"

Step 3: Start Traefik

cd /opt/stacks/proxy/traefik
docker compose up -d

# Verify
docker compose ps

Phase 2: Migrate AI Services

AI Service Labels (Add to litellm, chat, mcp-compose.yml)

services:
  litellm:
    image: litellm
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.litellm.rule=Host(`litellm.nuclide.systems`)"
      - "traefik.http.routers.litellm.entrypoints=websecure"
      - "traefik.http.routers.litellm.tls=true"
      - "traefik.http.routers.litellm.tls.certresolver=letsencrypt"
      - "traefik.http.routers.litellm.priority=10"
      - "traefik.http.services.litellm.loadbalancer.server.port=14000"

  chat:
    image: lobehub
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chat.rule=Host(`chat.nuclide.systems`)"
      - "traefik.http.routers.chat.entrypoints=websecure"
      - "traefik.http.routers.chat.tls=true"
      - "traefik.http.routers.chat.tls.certresolver=letsencrypt"
      - "traefik.http.routers.chat.priority=10"
      - "traefik.http.services.chat.loadbalancer.server.port=14001"

  mcp:
    image: mcp-gateway
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`mcp.nuclide.systems`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
      - "traefik.http.routers.mcp.priority=10"
      - "traefik.http.services.mcp.loadbalancer.server.port=8080"

Phase 3: Migrate Garage S3

Option A: Use Traefik Proxy

services:
  garage-proxy:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.s3.rule=Host(`s3.nuclide.systems`)"
      - "traefik.http.routers.s3.entrypoints=websecure"
      - "traefik.http.routers.s3.tls=true"
      - "traefik.http.routers.s3.tls.certresolver=letsencrypt"
      - "traefik.http.services.s3.loadbalancer.server.port=10004"
    volumes:
      - garage-data:/data
    networks:
      - shared_backend

Option B: Keep Internal Garage Access

services:
  # No proxy needed - access Garage via internal IP:10004
  garage:
    image: garageio/garage
    ports:
      - "3900:3900"   # Internal only
      - "10004:10004" # Public via Traefik

Phase 4: Create Helper Scripts

script 1: Add Service to Traefik

#!/bin/bash
# /opt/stacks/scripts/add-traefik-service.sh

NAME=$1
DOMAIN=$2
PORT=$3

cat > /opt/stacks/proxy/traefik/dynamic/${NAME}.yml << EOF
http:
  routers:
    ${NAME}-router:
      rule: "Host(\`${DOMAIN}\`)"
      service: ${NAME}-service
      entrypoints:
        - websecure
      tls:
        certresolver: letsencrypt

  services:
    ${NAME}-service:
      loadBalancer:
        servers:
          - url: "http://192.168.1.40:${PORT}"
EOF

# Reload Traefik docker automatically (no manual step needed)
echo "✅ Service ${NAME} added via labels"

Usage:

/opt/stacks/scripts/add-traefik-service.sh myservice myservice.nuclide.systems 8000

Script 2: Generate Labels for Existing Services

#!/bin/bash
# /opt/stacks/scripts/traefik-labels-gen.sh

cat > /opt/stacks/proxy/traefik/labels.yaml << 'EOF'
# Add these labels to service docker-compose.yml files

# LiteLLM
services:
  litellm:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.litellm.rule=Host(`litellm.nuclide.systems`)"
      - "traefik.http.routers.litellm.entrypoints=websecure"
      - "traefik.http.routers.litellm.tls=true"
      - "traefik.http.routers.litellm.tls.certresolver=letsencrypt"
      - "traefik.http.services.litellm.loadbalancer.server.port=14000"

# LobeHub Chat
services:
  chat:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chat.rule=Host(`chat.nuclide.systems`)"
      - "traefik.http.routers.chat.entrypoints=websecure"
      - "traefik.http.routers.chat.tls=true"
      - "traefik.http.routers.chat.tls.certresolver=letsencrypt"
      - "traefik.http.services.chat.loadbalancer.server.port=14001"

# MCP Gateway
services:
  mcp-gateway:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mcp.rule=Host(`mcp.nuclide.systems`)"
      - "traefik.http.routers.mcp.entrypoints=websecure"
      - "traefik.http.routers.mcp.tls=true"
      - "traefik.http.routers.mcp.tls.certresolver=letsencrypt"
      - "traefik.http.services.mcp.loadbalancer.server.port=8080"
EOF

echo "✅ Labels saved to /opt/stacks/proxy/traefik/labels.yaml"

Usage:

/opt/stacks/scripts/traefik-labels-gen.sh

Phase 5: Update Service Configs

Update /opt/stacks/ai/.env

# OLD (Zoraxy):
LITELLM_BASE_URL=https://litellm.nuclide.systems
PROXY_BASE_URL=https://mcp.nuclide.systems

# NEW (Traefik) - same URLs, different backend:
LITELLM_BASE_URL=https://litellm.nuclide.systems
PROXY_BASE_URL=https://mcp.nuclide.systems
CHATAI_BASE_URL=https://chat.nuclide.systems

Update /opt/stacks/ai/litellm-config/config.yaml

general_settings:
  proxy_base_url: https://litellm.nuclide.systems
  control_plane_url: https://litellm.nuclide.systems

Phase 6: Verify SSL

Step 1: Generate Let's Encrypt Certificates

# Verify Traefik is running
docker compose ps traefik

# Create ACME cert file
touch /opt/stacks/proxy/traefik/letsencrypt/acme.json
chmod 600 /opt/stacks/proxy/traefik/letsencrypt/acme.json

# Trigger certificate generation (will happen automatically)
# Check status:
curl -s https://acme-v02.api.letsencrypt.org/directory | head

Step 2: Test HTTPS

# Test endpoints
curl -k https://litellm.nuclide.systems/health
curl -k https://chat.nuclide.systems/health
curl -k https://mcp.nuclide.systems/health

# Verify cert
curl -v https://litellm.nuclide.systems 2>&1 | grep -A 5 "SSL certificate"

Phase 7: Remove Zoraxy

Backup first

# Backup Zoraxy configs
docker cp zoraxy:/data/configs /backup/zoraxy-backup/

# Optional: Stop Zoraxy
docker stop zoraxy
docker rm zoraxy

Quick Migration Checklist

  • Install Traefik (Phase 1)
  • Add labels to AI services (Phase 2)
  • Add Garage S3 proxy (Phase 3)
  • Run label generation script (Phase 4)
  • Update .env and config files (Phase 5)
  • Wait for SSL certificates (auto 24-48h)
  • Test all HTTPS endpoints
  • Remove Zoraxy (Phase 7)
  • Update AdGuard DNS if needed (optional)

Monitoring & Troubleshooting

Check Traefik Dashboard

# Access web UI (unsecured - use only on trusted network)
open http://192.168.1.40:8080/dashboard

# View all routers
curl http://localhost:8080/api/http/routers | jq '.[] | {name: .rule, status: .entryPoints}'

# View all services
curl http://localhost:8080/api/http/services | jq '.[] | {name: .name, servers: .servers}'

Common Issues

Issue Solution
404 errors Check router labels match domain exactly
SSL expired Wait for auto-renew or trigger manually
Port mismatch Verify loadbalancer.server.port matches service
No SSL cert Check email in acme.json config

Rollback Plan (If Needed)

# Stop Traefik
docker compose -f /opt/stacks/proxy/traefik/docker-compose.yml down

# Restore Zoraxy configs
docker cp /backup/zoraxy-backup/ configs/

# Restart Zoraxy (if you kept backup)
docker start zoraxy || true