Files
launchpad-gateway/docker-compose.yml

393 lines
16 KiB
YAML

########################
# Networks & Volumes
########################
networks:
traefik_proxy:
name: traefik_proxy
volumes:
traefik_letsencrypt:
traefik_logs:
portainer_data:
uptime_kuma_data:
umami_data:
pgadmin_data:
authelia_db_data:
beszel_data:
gitea_data:
gitea_db_data:
########################
# Services
########################
services:
## ─────────────────────────────────────────────
## Traefik — edge router + ACME (HTTP-01)
## ─────────────────────────────────────────────
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
# Mail protocol ports for MailCow integration
- "25:25" # SMTP
- "465:465" # SMTPS
- "587:587" # Submission
- "143:143" # IMAP
- "993:993" # IMAPS
- "110:110" # POP3
- "995:995" # POP3S
- "4190:4190" # ManageSieve
networks: [traefik_proxy]
environment:
TZ: "${TZ}"
command:
# Enable ping endpoint for health checks
- --ping=true
# Experimental plugins
- --experimental.plugins.traefik-umami-plugin.modulename=github.com/1cedsoda/traefik-umami-plugin
- --experimental.plugins.traefik-umami-plugin.version=v1.0.3
# Providers
- --providers.docker=true
- --providers.docker.exposedbydefault=false
# Entrypoints
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.web.forwardedheaders.insecure=true
- --entrypoints.websecure.forwardedheaders.insecure=true
# Mail protocol entrypoints for MailCow integration
- --entrypoints.smtp.address=:25
- --entrypoints.smtps.address=:465
- --entrypoints.submission.address=:587
- --entrypoints.imap.address=:143
- --entrypoints.imaps.address=:993
- --entrypoints.pop3.address=:110
- --entrypoints.pop3s.address=:995
- --entrypoints.sieve.address=:4190
# Dashboard/API (internal)
- --api=true
- --api.dashboard=true
# ACME via HTTP-01 (no registrar API needed)
- --certificatesresolvers.le.acme.email=${ACME_EMAIL}
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
# (Alt) Use TLS-ALPN-01 if port 80 is blocked:
# - --certificatesresolvers.le.acme.tlschallenge=true
# Global timeouts for slow backends
- --serversTransport.forwardingTimeouts.dialTimeout=30s
- --serversTransport.forwardingTimeouts.responseHeaderTimeout=60s
- --serversTransport.forwardingTimeouts.idleConnTimeout=180s
# Logs
- --accesslog.filepath=/var/log/traefik/access.log
- --accesslog.bufferingsize=100
- --log.level=INFO
- --metrics.prometheus=true
volumes:
- /var/run/docker.sock:/var/rundocker.sock:ro
- traefik_letsencrypt:/letsencrypt
- traefik_logs:/var/log/traefik
labels:
- "traefik.enable=true"
# Reusable security headers middleware
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.stsPreload=true"
- "traefik.http.middlewares.security-headers.headers.browserXssFilter=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security-headers.headers.referrerPolicy=no-referrer-when-downgrade"
# Authelia middleware
- "traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/authz/forward-auth"
- "traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true"
- "traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Email,Remote-Name"
# Traefik dashboard (protected)
- "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.tls.certresolver=le"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.middlewares=authelia@docker,security-headers@docker"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Authelia — authentication and authorization
## ─────────────────────────────────────────────
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- ./authelia:/config
entrypoint: /bin/sh
command:
- -c
- |
eval "echo \"$(cat /config/configuration.template.yml)\"" > /config/configuration.yml
exec /app/entrypoint.sh
environment:
TZ: "${TZ}"
DOMAIN: "${DOMAIN}"
DOMAIN_PREFIX: "${DOMAIN_PREFIX}"
AUTHELIA_SESSION_SECRET: '${AUTHELIA_SESSION_SECRET}'
AUTHELIA_STORAGE_ENCRYPTION_KEY: '${AUTHELIA_STORAGE_ENCRYPTION_KEY}'
AUTHELIA_STORAGE_POSTGRES_PASSWORD: '${AUTHELIA_DB_PASSWORD}'
AUTHELIA_NOTIFIER_SMTP_PASSWORD: '${AUTHELIA_NOTIFIER_SMTP_PASSWORD}'
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET: '${AUTHELIA_JWT_SECRET}'
depends_on:
- authelia-db
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia.rule=Host(`auth.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.authelia.entrypoints=websecure"
- "traefik.http.routers.authelia.tls.certresolver=le"
- "traefik.http.routers.authelia.middlewares=security-headers@docker"
- "traefik.http.services.authelia.loadbalancer.server.port=9091"
## ─────────────────────────────────────────────
## Authelia Database — PostgreSQL
## ─────────────────────────────────────────────
authelia-db:
image: postgres:15-alpine
container_name: authelia-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: authelia
POSTGRES_USER: authelia
POSTGRES_PASSWORD: ${AUTHELIA_DB_PASSWORD}
volumes:
- authelia_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U authelia -d authelia"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
## ─────────────────────────────────────────────
## Portainer — Docker control plane
## ─────────────────────────────────────────────
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
networks: [traefik_proxy]
command: --host unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=le"
- "traefik.http.routers.portainer.middlewares=security-headers@docker"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
## ─────────────────────────────────────────────
## Uptime Kuma — status page / checks
## ─────────────────────────────────────────────
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
volumes:
- uptime_kuma_data:/app/data
networks: [traefik_proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.kuma.rule=Host(`uptime.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.kuma.entrypoints=websecure"
- "traefik.http.routers.kuma.tls.certresolver=le"
- "traefik.http.routers.kuma.middlewares=security-headers@docker"
- "traefik.http.services.kuma.loadbalancer.server.port=3001"
## ─────────────────────────────────────────────
## Umami — web analytics
## ─────────────────────────────────────────────
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
restart: unless-stopped
networks: [traefik_proxy]
environment:
DATABASE_URL: postgresql://${UMAMI_DB_USER}:${UMAMI_DB_PASS}@umami-db:5432/${UMAMI_DB_NAME}
DATABASE_TYPE: postgresql
APP_SECRET: ${UMAMI_APP_SECRET}
depends_on:
- umami-db
labels:
- "traefik.enable=true"
- "traefik.http.routers.umami.rule=Host(`umami.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.umami.entrypoints=websecure"
- "traefik.http.routers.umami.tls.certresolver=le"
- "traefik.http.routers.umami.middlewares=security-headers@docker"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Umami Database — PostgreSQL
## ─────────────────────────────────────────────
umami-db:
image: postgres:15-alpine
container_name: umami-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: ${UMAMI_DB_NAME}
POSTGRES_USER: ${UMAMI_DB_USER}
POSTGRES_PASSWORD: ${UMAMI_DB_PASS}
volumes:
- umami_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${UMAMI_DB_USER} -d ${UMAMI_DB_NAME}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ─────────────────────────────────────────────
# pgAdmin — PostgreSQL administration
# ─────────────────────────────────────────────
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: unless-stopped
networks: [traefik_proxy]
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: 'True'
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
PGADMIN_CONFIG_WTF_CSRF_CHECK_DEFAULT: 'False'
PGADMIN_CONFIG_WTF_CSRF_TIME_LIMIT: 'None'
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'False'
PGADMIN_CONFIG_PROXY_X_HOST_COUNT: '1'
PGADMIN_CONFIG_PROXY_X_PREFIX_COUNT: '1'
volumes:
- pgadmin_data:/var/lib/pgadmin
labels:
- "traefik.enable=true"
- "traefik.http.routers.pgadmin.rule=Host(`pgadmin.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.pgadmin.entrypoints=websecure"
- "traefik.http.routers.pgadmin.tls.certresolver=le"
- "traefik.http.routers.pgadmin.middlewares=security-headers@docker"
- "traefik.http.services.pgadmin.loadbalancer.server.port=80"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Beszel Hub — lightweight server monitoring
## ─────────────────────────────────────────────
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
networks: [traefik_proxy]
volumes:
- beszel_data:/beszel_data
labels:
- "traefik.enable=true"
- "traefik.http.routers.beszel.rule=Host(`beszel.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.beszel.entrypoints=websecure"
- "traefik.http.routers.beszel.tls.certresolver=le"
- "traefik.http.routers.beszel.middlewares=security-headers@docker"
- "traefik.http.services.beszel.loadbalancer.server.port=8090"
## ─────────────────────────────────────────────
## Gitea — self-hosted Git service
## ─────────────────────────────────────────────
gitea:
image: docker.gitea.com/gitea:latest
container_name: gitea
restart: unless-stopped
networks: [traefik_proxy]
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__NAME=${GITEA_DB_NAME}
- GITEA__database__USER=${GITEA_DB_USER}
- GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
- GITEA__database__SSL_MODE=disable
- GITEA__server__DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN}
- GITEA__server__SSH_DOMAIN=git.${DOMAIN_PREFIX}.${DOMAIN}
- GITEA__server__ROOT_URL=https://git.${DOMAIN_PREFIX}.${DOMAIN}/
- GITEA__server__SSH_PORT=222
- GITEA__server__SSH_LISTEN_PORT=22
- GITEA__security__SECRET_KEY=${GITEA_SECRET_KEY}
- GITEA__security__INTERNAL_TOKEN=${GITEA_INTERNAL_TOKEN}
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "222:22"
depends_on:
- gitea-db
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`git.${DOMAIN_PREFIX}.${DOMAIN}`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=le"
- "traefik.http.routers.gitea.middlewares=security-headers@docker,authelia@docker"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
## ─────────────────────────────────────────────
## Gitea Database — PostgreSQL
## ─────────────────────────────────────────────
gitea-db:
image: postgres:15-alpine
container_name: gitea-db
restart: unless-stopped
networks: [traefik_proxy]
environment:
POSTGRES_DB: ${GITEA_DB_NAME}
POSTGRES_USER: ${GITEA_DB_USER}
POSTGRES_PASSWORD: ${GITEA_DB_PASSWORD}
volumes:
- gitea_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${GITEA_DB_USER} -d ${GITEA_DB_NAME}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s