# Coolify Compose Template — TheHomelessSherlock ## Proxy & Networking Rules ### Our Setup - **Proxy**: `coolify-proxy` (Traefik v3) configured with `--providers.docker.network=proxy` - **TLS**: certresolver `letsencrypt` via HTTP challenge - **Domain routing**: configured in Coolify UI, NOT in compose labels ### Rule 1 — Every service exposed via HTTP/HTTPS must be on the `proxy` network ```yaml services: myapp: image: myimage:latest pull_policy: always networks: - internal - proxy # ← mandatory for Traefik to reach it labels: traefik.http.services.myapp.loadbalancer.server.port: "3000" # ← mandatory port label networks: internal: driver: bridge proxy: external: true # ← always external, pre-created by Coolify ``` ### Rule 2 — Internal-only services do NOT need proxy ```yaml services: mydb: image: postgres:16 networks: - internal # ← only internal, no proxy networks: internal: driver: bridge ``` ### Rule 3 — Mail-relay access via mail_internal network Services that need to send mail via the internal mail-relay must be on `mail_internal`: ```yaml services: myapp: networks: - internal - proxy - mail_internal # ← only services that need mail environment: SMTP_HOST: mail-relay # ← container name as hostname SMTP_PORT: 587 networks: mail_internal: external: true # ← pre-created: docker network create mail_internal ``` ### Rule 4 — Labels: only port + optional middleware Coolify manages routing labels automatically. Only set: ```yaml labels: # MANDATORY: tell Traefik which port your app listens on traefik.http.services.myapp.loadbalancer.server.port: "3000" # OPTIONAL: define reusable middleware (e.g. security headers, redirects) traefik.http.middlewares.myapp-headers.headers.stsSeconds: "31536000" traefik.http.middlewares.myapp-headers.headers.stsIncludeSubdomains: "true" ``` **NEVER add these** (Coolify adds them automatically based on UI config): - `traefik.enable` - `traefik.docker.network` - `traefik.http.routers.*` ### Rule 5 — pull_policy: always on main service Ensures the latest image is pulled on every deploy: ```yaml services: myapp: image: myimage:latest pull_policy: always # ← add to main/frontend service only ``` ### Rule 6 — Authentik middleware To protect a service with Authentik SSO, use the middleware defined in the authentik stack: In Coolify UI: add this label to your application under "Labels": ``` traefik.http.routers..middlewares=ths-authentik@docker ``` Or in your compose labels (will be merged with Coolify's auto-labels): ```yaml labels: traefik.http.services.myapp.loadbalancer.server.port: "3000" # The router name Coolify generates follows: https-0-- # Use Coolify UI "Labels" field to add the middleware after first deploy ``` ## Full Example — Minimal web app with DB and mail ```yaml services: myapp: image: myimage:latest pull_policy: always restart: unless-stopped depends_on: - myapp-db environment: DB_HOST: myapp-db DB_PORT: 5432 SMTP_HOST: mail-relay SMTP_PORT: 587 networks: - internal - proxy - mail_internal labels: traefik.http.services.myapp.loadbalancer.server.port: "3000" myapp-db: image: postgres:16 restart: unless-stopped environment: POSTGRES_DB: myapp POSTGRES_USER: myapp POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - /opt/myapp/db:/var/lib/postgresql/data:Z networks: - internal networks: internal: driver: bridge proxy: external: true mail_internal: external: true ``` ## Pre-created networks on the host These networks must exist before deploying stacks that use them: ```bash # Already created by Coolify: # docker network create proxy ← created as part of Coolify install # Create manually once: docker network create mail_internal ``` ## Coolify UI checklist per application 1. **Ports Exposes**: set to the app's HTTP port (must match `loadbalancer.server.port` label) 2. **Domain**: set FQDN (e.g. `myapp.sherlockhomeless.net`) 3. **Base Directory**: set to the subdirectory (e.g. `/gitea`, `/n8n`) 4. **Environment Variables**: fill from `stack.env` template --- ## Gotchas del sistema — leer antes de tocar el proxy ### Gotcha 1 — Directorio dynamic del proxy NO es el de Coolify El proxy `coolify-proxy` monta su directorio de configuración dinámica desde: ``` /opt/traefik/dynamic → /dynamic (dentro del contenedor) ``` **NO** desde `/data/coolify/proxy/dynamic/` (ese directorio existe pero Traefik NO lo lee). Si necesitas añadir rutas manuales (por ejemplo, para el dashboard de Coolify), edita: ```bash /opt/traefik/dynamic/coolify.yaml # ← aquí es donde importa ``` ### Gotcha 2 — Entrypoints del proxy: `http` y `https` (NO `web`/`websecure`) Coolify genera automáticamente labels de Traefik con: - `entryPoints=http` (puerto 80) - `entryPoints=https` (puerto 443) El proxy (`/data/coolify/proxy/docker-compose.yml`) está configurado con esos mismos nombres. El contenedor `coolify` propio tiene labels antiguas con `websecure` — no importa porque su routing está definido en `/opt/traefik/dynamic/coolify.yaml`. **Si el dashboard de Coolify da 404**, verificar: 1. Que `/opt/traefik/dynamic/coolify.yaml` existe y tiene routers `http`/`https` 2. Que `coolify-proxy` puede resolver `coolify:8080` (ambos en red `proxy`) ### Gotcha 3 — Variables en bind mounts → Coolify las convierte en volúmenes nombrados ```yaml # ❌ MAL: Coolify NO resuelve la variable → crea volumen Docker nombrado vacío volumes: - ${MY_DATA_PATH}:/var/lib/postgresql/data:Z # ✅ BIEN: rutas hardcodeadas volumes: - /opt/myapp/postgres:/var/lib/postgresql/data:Z ``` ### Gotcha 4 — SSH MaxSessions `/etc/ssh/sshd_config` tiene `MaxSessions 20` (cambiado de 2). Coolify abre múltiples sesiones SSH en paralelo durante el deploy. Si vuelve a bajar a 2 (upgrade del OS, etc.), todos los deploys fallarán con exit code 255. ```bash grep MaxSessions /etc/ssh/sshd_config # debe ser >= 10 ``` ### Gotcha 5 — Todos los datos del host están en /opt// Convención de este servidor: todos los bind mounts van bajo `/opt//`. Antes de hardcodear rutas en un compose, verificar siempre: ```bash ls /opt// # ej: /opt/adguard/, /opt/authentik/, /opt/gitea/, etc. ``` Si existe el directorio con datos → usar bind mount a esa ruta. Si no existe → crear el directorio antes de desplegar, o usar named volume.