238 lines
6.6 KiB
Markdown
238 lines
6.6 KiB
Markdown
# 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.<your-router-name>.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-<uuid>-<servicename>
|
|
# 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/<stack>/
|
|
|
|
Convención de este servidor: todos los bind mounts van bajo `/opt/<nombre-stack>/`.
|
|
Antes de hardcodear rutas en un compose, verificar siempre:
|
|
|
|
```bash
|
|
ls /opt/<stack>/ # 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.
|