# traefik/-weight: 500;">docker-compose.yml
services: traefik: image: traefik:v3.0 container_name: traefik -weight: 500;">restart: unless-stopped command: - "--api.insecure=false" - "--providers.-weight: 500;">docker=true" - "--providers.-weight: 500;">docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers[email protected]" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - ./letsencrypt:/letsencrypt networks: - proxy networks: proxy: external: true
# traefik/-weight: 500;">docker-compose.yml
services: traefik: image: traefik:v3.0 container_name: traefik -weight: 500;">restart: unless-stopped command: - "--api.insecure=false" - "--providers.-weight: 500;">docker=true" - "--providers.-weight: 500;">docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers[email protected]" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - ./letsencrypt:/letsencrypt networks: - proxy networks: proxy: external: true
# traefik/-weight: 500;">docker-compose.yml
services: traefik: image: traefik:v3.0 container_name: traefik -weight: 500;">restart: unless-stopped command: - "--api.insecure=false" - "--providers.-weight: 500;">docker=true" - "--providers.-weight: 500;">docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers[email protected]" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - ./letsencrypt:/letsencrypt networks: - proxy networks: proxy: external: true
services: portainer: image: portainer/portainer-ce:latest container_name: portainer -weight: 500;">restart: unless-stopped volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - portainer_data:/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.yourdomain.com`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" networks: - proxy
services: portainer: image: portainer/portainer-ce:latest container_name: portainer -weight: 500;">restart: unless-stopped volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - portainer_data:/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.yourdomain.com`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" networks: - proxy
services: portainer: image: portainer/portainer-ce:latest container_name: portainer -weight: 500;">restart: unless-stopped volumes: - /var/run/-weight: 500;">docker.sock:/var/run/-weight: 500;">docker.sock:ro - portainer_data:/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.yourdomain.com`)" - "traefik.http.routers.portainer.entrypoints=websecure" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" networks: - proxy
services: bookstack: image: lscr.io/linuxserver/bookstack:latest container_name: bookstack -weight: 500;">restart: unless-stopped environment: - APP_URL=https://docs.yourdomain.com - DB_HOST=bookstack-db - DB_PASS=${DB_PASS} volumes: - ./config:/config networks: - proxy - internal bookstack-db: image: mariadb:10.11 networks: - internal
services: bookstack: image: lscr.io/linuxserver/bookstack:latest container_name: bookstack -weight: 500;">restart: unless-stopped environment: - APP_URL=https://docs.yourdomain.com - DB_HOST=bookstack-db - DB_PASS=${DB_PASS} volumes: - ./config:/config networks: - proxy - internal bookstack-db: image: mariadb:10.11 networks: - internal
services: bookstack: image: lscr.io/linuxserver/bookstack:latest container_name: bookstack -weight: 500;">restart: unless-stopped environment: - APP_URL=https://docs.yourdomain.com - DB_HOST=bookstack-db - DB_PASS=${DB_PASS} volumes: - ./config:/config networks: - proxy - internal bookstack-db: image: mariadb:10.11 networks: - internal
services: vaultwarden: image: vaultwarden/server:latest environment: - DOMAIN=https://vault.yourdomain.com - SIGNUPS_ALLOWED=false - ADMIN_TOKEN=${ADMIN_TOKEN} volumes: - ./vw-data:/data
services: vaultwarden: image: vaultwarden/server:latest environment: - DOMAIN=https://vault.yourdomain.com - SIGNUPS_ALLOWED=false - ADMIN_TOKEN=${ADMIN_TOKEN} volumes: - ./vw-data:/data
services: vaultwarden: image: vaultwarden/server:latest environment: - DOMAIN=https://vault.yourdomain.com - SIGNUPS_ALLOWED=false - ADMIN_TOKEN=${ADMIN_TOKEN} volumes: - ./vw-data:/data
services: uptime-kuma: image: louislam/uptime-kuma:1 volumes: - uptime-kuma:/app/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.uptime-kuma.rule=Host(`-weight: 500;">status.yourdomain.com`)"
services: uptime-kuma: image: louislam/uptime-kuma:1 volumes: - uptime-kuma:/app/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.uptime-kuma.rule=Host(`-weight: 500;">status.yourdomain.com`)"
services: uptime-kuma: image: louislam/uptime-kuma:1 volumes: - uptime-kuma:/app/data labels: - "traefik.-weight: 500;">enable=true" - "traefik.http.routers.uptime-kuma.rule=Host(`-weight: 500;">status.yourdomain.com`)"
services: gitea: image: gitea/gitea:latest environment: - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=gitea-db:5432 networks: - proxy - internal gitea-db: image: postgres:15 networks: - internal
services: gitea: image: gitea/gitea:latest environment: - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=gitea-db:5432 networks: - proxy - internal gitea-db: image: postgres:15 networks: - internal
services: gitea: image: gitea/gitea:latest environment: - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=gitea-db:5432 networks: - proxy - internal gitea-db: image: postgres:15 networks: - internal
services: prometheus: image: prom/prometheus:latest networks: - internal grafana: image: grafana/grafana:latest environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASS} - GF_USERS_ALLOW_SIGN_UP=false networks: - proxy - internal
services: prometheus: image: prom/prometheus:latest networks: - internal grafana: image: grafana/grafana:latest environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASS} - GF_USERS_ALLOW_SIGN_UP=false networks: - proxy - internal
services: prometheus: image: prom/prometheus:latest networks: - internal grafana: image: grafana/grafana:latest environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASS} - GF_USERS_ALLOW_SIGN_UP=false networks: - proxy - internal - Secrets stay out of compose files. Use a .env file. Add .env to .gitignore immediately.
- Separate networks by trust level. Public-facing services on the proxy network. Database containers on internal-only networks.
- Run scheduled security scanning. Trivy and Docker Bench for Security are worth running regularly.
- Back up volumes, not just images. The image is replaceable. Your BookStack content and Vaultwarden data are not.
- Pin image versions in production. latest can introduce breaking changes on pull.