← Back to Infrastructure Documentation

Service Deployment Patterns

Standard patterns for deploying services on linuxserver.lan / ai-servicers.com

← Portal Home 🏢 Infrastructure Documentation 🔐 Security & Auth 🌐 Network Topology

Standard Project Structure

/home/administrator/projects/[service]/
  deploy.sh # Deployment script (required)
  docker-compose.yml # Optional compose file
  CLAUDE.md # AI context documentation
  README.md # Human documentation
  configs/ # Service-specific configs

/home/administrator/projects/secrets/
  [service].env # Environment variables

/home/administrator/projects/data/[service]/
  # Persistent data storage

Key Principles

  • Secrets NEVER in project directory
  • Data stored in centralized data/ folder
  • Every service has a deploy.sh
  • CLAUDE.md for AI context
  • Consistent naming throughout
  • Networks created in deploy.sh

deploy.sh Anatomy

#!/bin/bash
set -e  # Exit on error

# ===== Configuration =====
PROJECT_DIR="/home/administrator/projects/myservice"
SECRETS_FILE="$HOME/projects/secrets/myservice.env"
DATA_DIR="$HOME/projects/data/myservice"

# ===== Validate Secrets =====
if [ ! -f "$SECRETS_FILE" ]; then
    echo "ERROR: Secrets file not found"
    exit 1
fi
source "$SECRETS_FILE"

# ===== Create Directories =====
mkdir -p "$DATA_DIR"

# ===== Create Networks =====
docker network create traefik-net 2>/dev/null || true
docker network create myservice-net 2>/dev/null || true

# ===== Stop Existing =====
docker stop myservice 2>/dev/null || true
docker rm myservice 2>/dev/null || true

# ===== Deploy Container =====
docker run -d \
  --name myservice \
  --restart unless-stopped \
  --network traefik-net \
  -v "$DATA_DIR:/data" \
  -e SECRET_KEY="$SECRET_KEY" \
  --label "traefik.enable=true" \
  --label "traefik.http.routers.myservice.rule=Host(\`myservice.ai-servicers.com\`)" \
  --label "traefik.http.routers.myservice.entrypoints=websecure" \
  --label "traefik.http.routers.myservice.tls.certresolver=letsencrypt" \
  --label "traefik.http.services.myservice.loadbalancer.server.port=8080" \
  myimage:latest

# ===== Connect Additional Networks =====
docker network connect myservice-net myservice

echo "Deployed: https://myservice.ai-servicers.com"

Network Selection Patterns

3-Network Pattern (OAuth2 Protected)

For services requiring SSO authentication

traefik-net keycloak-net [service]-net

Examples: grafana, portainer, obsidian, dashy

2-Network Pattern (Database Access)

For services needing database connectivity

traefik-net postgres-net

Examples: keycloak, nextcloud, openproject

1-Network Pattern (Simple Service)

For standalone web services

traefik-net

Examples: static sites, simple APIs, tools

Network Selection Decision Tree

  • Does it need HTTPS? > Add to traefik-net
  • Does it need SSO? > Add to keycloak-net + deploy OAuth2 proxy
  • Does it need PostgreSQL? > Add to postgres-net
  • Does it need Redis? > Add to redis-net
  • Does it need MongoDB? > Add to mongodb-net
  • Is it part of MCP? > Add to mcp-net
  • Create service-specific network for internal component isolation

Traefik Integration

Label Purpose Example Value
traefik.enable Enable Traefik routing true
traefik.http.routers.[name].rule Routing rule (hostname) Host(`service.ai-servicers.com`)
traefik.http.routers.[name].entrypoints Entry point (HTTP/HTTPS) websecure
traefik.http.routers.[name].tls.certresolver SSL certificate provider letsencrypt
traefik.http.services.[name].loadbalancer.server.port Container port to route to 8080
traefik.docker.network Network for routing (when on multiple) traefik-net

Common Traefik Gotcha

If your container is on multiple networks, you MUST specify traefik.docker.network=traefik-net or Traefik may pick the wrong network and fail to route.

Secrets Management

Environment File Pattern

# /home/administrator/projects/secrets/myservice.env

# Container settings
PUID=1000
PGID=1000
TZ=America/New_York

# Application secrets
SECRET_KEY=your-secret-here
DATABASE_URL=postgres://user:pass@postgres:5432/db

# OAuth2 (if protected)
OAUTH2_PROXY_CLIENT_ID=myservice
OAUTH2_PROXY_CLIENT_SECRET=keycloak-secret
OAUTH2_PROXY_COOKIE_SECRET=32-byte-secret

Security Rules

  • NEVER store secrets in project directory
  • NEVER commit secrets to git
  • Always use $HOME/projects/secrets/ or $HOME/projects/secrets/
  • Load via: source "$SECRETS_FILE"
  • Pass to container via -e flags
  • Generate cookie secrets: python3 -c "import secrets; print(secrets.token_urlsafe(32))"

OAuth2 Protection (Quick Reference)

For full details, see Security & Auth Documentation

OAuth2 Proxy Container Pattern
docker run -d \
  --name myservice-auth-proxy \
  --network traefik-net \
  --network keycloak-net \
  -e OAUTH2_PROXY_PROVIDER=keycloak-oidc \
  -e OAUTH2_PROXY_CLIENT_ID="$CLIENT_ID" \
  -e OAUTH2_PROXY_CLIENT_SECRET="$CLIENT_SECRET" \
  -e OAUTH2_PROXY_COOKIE_SECRET="$COOKIE_SECRET" \
  -e OAUTH2_PROXY_UPSTREAMS="http://myservice:8080/" \
  -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \
  -e OAUTH2_PROXY_OIDC_ISSUER_URL="https://keycloak.ai-servicers.com/realms/master" \
  -e OAUTH2_PROXY_LOGIN_URL="https://keycloak.ai-servicers.com/realms/master/protocol/openid-connect/auth" \
  -e OAUTH2_PROXY_REDEEM_URL="http://keycloak:8080/realms/master/protocol/openid-connect/token" \
  -e OAUTH2_PROXY_OIDC_JWKS_URL="http://keycloak:8080/realms/master/protocol/openid-connect/certs" \
  --label "traefik.enable=true" \
  --label "traefik.http.routers.myservice.rule=Host(\`myservice.ai-servicers.com\`)" \
  quay.io/oauth2-proxy/oauth2-proxy:latest

Deployment Checklist

  1. Create project directory

    mkdir -p /home/administrator/projects/[service]

  2. Create secrets file

    Create /home/administrator/projects/secrets/[service].env with required variables

  3. Create data directory

    mkdir -p /home/administrator/projects/data/[service]

  4. Write deploy.sh

    Use the pattern above, customize for your service

  5. If OAuth2 protected: Create Keycloak client

    Create client in Keycloak, add secret to env file

  6. Deploy and verify

    ./deploy.sh && docker logs [service] --tail 20

  7. Test external access

    curl -I https://[service].ai-servicers.com

  8. Create CLAUDE.md documentation

    Document the service for AI context

Common Operations

Service Management

# Deploy/redeploy
cd /home/administrator/projects/[service]
./deploy.sh

# Check status
docker ps | grep [service]

# View logs
docker logs [service] --tail 50 -f

# Restart
docker restart [service]

# Stop and remove
docker stop [service] && docker rm [service]

Troubleshooting

# Check container networks
docker inspect [service] \
  --format='{{range $k,$v:=.NetworkSettings.Networks}}{{$k}} {{end}}'

# Test internal connectivity
docker exec [container] ping -c 1 [target]

# Check Traefik routing
curl -I https://[service].ai-servicers.com

# View Traefik logs
docker logs traefik --tail 50 | grep [service]