Files
headquarter/docker-compose.traefik.yml
T
Fusion b40eb3e88c feat(cloudflare-tunnel): integrate Cloudflare tunnels for instance access
Backend:
- Add cloudflare_tunnel.py service for creating/deleting tunnels via Cloudflare API
- Add public_url and tunnel_id fields to ToolInstance model
- Update start_instance to create Cloudflare tunnel after container starts
- Update stop_instance to delete tunnel before stopping container
- Update delete_instance to cleanup tunnel before deletion
- Update restart_instance to recreate tunnel on restart
- Create Alembic migration 0011 for tunnel fields
- Add Cloudflare config settings (API token, zone ID, account ID, base domain)

Infrastructure:
- Add cloudflared service to docker-compose.traefik.yml
- Mount shared cloudflared_config volume between API and cloudflared containers
- Add Cloudflare env vars to API service

Frontend:
- Update instance Open button to handle both full URLs and proxy paths

The instance URL is now set to the Cloudflare tunnel public URL when available,
falling back to the API proxy path if tunnel creation fails.
2026-05-20 14:25:06 +02:00

149 lines
4.7 KiB
YAML

# Docker Compose for deployment behind an existing Traefik reverse proxy
# All domains and the proxy web name are configurable via environment variables
services:
# PostgreSQL Database
postgres:
image: postgres:15-alpine
container_name: hq-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-headquarter}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-headquarter}
POSTGRES_DB: ${POSTGRES_DB:-headquarter}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-headquarter} -d ${POSTGRES_DB:-headquarter}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- backend
restart: unless-stopped
# Redis Cache
redis:
image: redis:7-alpine
container_name: hq-redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
networks:
- backend
restart: unless-stopped
# Web Frontend
headquarter-frontend:
build:
context: ./apps/web
dockerfile: Dockerfile
args:
VITE_API_BASE_URL: ${API_PUBLIC_URL:-https://${API_DOMAIN}}
VITE_APP_URL: ${WEB_PUBLIC_URL:-https://${WEB_DOMAIN}}
container_name: hq-web
environment:
VITE_API_BASE_URL: ${API_PUBLIC_URL:-https://${API_DOMAIN}}
VITE_APP_URL: ${WEB_PUBLIC_URL:-https://${WEB_DOMAIN}}
depends_on:
- headquarter-api
networks:
- backend
- traefik
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.headquarter-frontend.rule=Host(`${WEB_DOMAIN}`)"
- "traefik.http.routers.headquarter-frontend.entrypoints=websecure"
- "traefik.http.routers.headquarter-frontend.tls=true"
- "traefik.http.routers.headquarter-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
- "traefik.http.services.headquarter-frontend.loadbalancer.server.port=80"
# API Service
headquarter-api:
build:
context: ./apps/api
dockerfile: Dockerfile
container_name: hq-api
environment:
APP_ENV: production
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-headquarter}:${POSTGRES_PASSWORD:-headquarter}@postgres:5432/${POSTGRES_DB:-headquarter}
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
REDIS_URL: redis://redis:6379/0
SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production}
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24}
REPO_BASE_PATH: /data/repos
INSTANCE_BASE_PATH: /data/instances
API_DOMAIN: ${API_DOMAIN}
WEB_DOMAIN: ${WEB_DOMAIN}
AUTHENTIK_DOMAIN: ${AUTHENTIK_DOMAIN}
API_PUBLIC_URL: ${API_PUBLIC_URL:-}
WEB_PUBLIC_URL: ${WEB_PUBLIC_URL:-}
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:-headquarter-web}
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
AUTHENTIK_APPLICATION_SLUG: ${AUTHENTIK_APPLICATION_SLUG:-headquarter-web}
AUTHENTIK_AUTHORIZE_URL: ${AUTHENTIK_AUTHORIZE_URL:-}
AUTHENTIK_TOKEN_URL: ${AUTHENTIK_TOKEN_URL:-}
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
CLOUDFLARE_ZONE_ID: ${CLOUDFLARE_ZONE_ID}
CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID}
CLOUDFLARE_BASE_DOMAIN: ${CLOUDFLARE_BASE_DOMAIN}
CLOUDFLARED_CONFIG_DIR: /etc/cloudflared
volumes:
- repo_data:/data/repos
- instance_data:/data/instances
- avatar_uploads:/app/uploads
- /var/run/docker.sock:/var/run/docker.sock
- cloudflared_config:/etc/cloudflared
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
- traefik
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.headquarter-api.rule=Host(`${API_DOMAIN}`)"
- "traefik.http.routers.headquarter-api.entrypoints=websecure"
- "traefik.http.routers.headquarter-api.tls=true"
- "traefik.http.routers.headquarter-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
- "traefik.http.services.headquarter-api.loadbalancer.server.port=8000"
# Cloudflare Tunnel
cloudflared:
image: cloudflare/cloudflared:latest
container_name: hq-cloudflared
command: tunnel --config /etc/cloudflared/config.yml run
volumes:
- cloudflared_config:/etc/cloudflared
networks:
- backend
restart: unless-stopped
depends_on:
- headquarter-api
volumes:
postgres_data:
redis_data:
repo_data:
instance_data:
avatar_uploads:
cloudflared_config:
networks:
backend:
driver: bridge
traefik:
external: true
name: ${TRAEFIK_NETWORK:-traefik}