2026-10-09 15:38:29 +03:30
|
|
|
# Production-style override: put the gateway (frontend + API) behind Traefik
|
|
|
|
|
# with automatic Let's Encrypt TLS. Use together with the base file:
|
|
|
|
|
#
|
|
|
|
|
# docker compose -f docker-compose.yml -f docker-compose.traefik.yml up -d --build
|
|
|
|
|
#
|
|
|
|
|
# Only Traefik publishes host ports (80/443). Everything else is reachable
|
|
|
|
|
# only on the compose network.
|
2026-10-19 20:31:10 +03:30
|
|
|
#
|
|
|
|
|
# Metrics: every Ledgerline service serves OTel metrics on :9464 (see
|
|
|
|
|
# common/telemetry.py). Traefik exposes each one as
|
|
|
|
|
# https://ledgerline.vione.cloud/metrics/<service>, behind basic auth, so the
|
|
|
|
|
# platform's Prometheus on falken can scrape it (k8s/prometheus/prometheus.yml,
|
|
|
|
|
# job `ledgerline`). The htpasswd file is written by k8s/create-secrets.sh and
|
|
|
|
|
# copied here once, like .env.observability.
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
services:
|
|
|
|
|
traefik:
|
|
|
|
|
image: traefik:v3.6
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
command:
|
|
|
|
|
- --providers.docker=true
|
|
|
|
|
- --providers.docker.exposedByDefault=false
|
|
|
|
|
- --entrypoints.web.address=:80
|
|
|
|
|
- --entrypoints.web.http.redirections.entryPoint.to=websecure
|
|
|
|
|
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
|
|
|
|
- --entrypoints.websecure.address=:443
|
|
|
|
|
- --certificatesresolvers.le.acme.httpchallenge=true
|
|
|
|
|
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
|
|
|
|
|
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
|
|
|
|
- --log.level=INFO
|
|
|
|
|
- --accesslog=true
|
|
|
|
|
ports:
|
|
|
|
|
- "80:80"
|
|
|
|
|
- "443:443"
|
|
|
|
|
volumes:
|
|
|
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
|
|
|
- letsencrypt:/letsencrypt
|
2026-10-19 20:31:10 +03:30
|
|
|
- ./observability/traefik/metrics-users:/etc/traefik/metrics-users:ro
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
gateway:
|
|
|
|
|
ports: !reset []
|
|
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.ledgerline.rule=Host(`ledgerline.vione.cloud`)
|
|
|
|
|
- traefik.http.routers.ledgerline.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.ledgerline.tls.certresolver=le
|
|
|
|
|
- traefik.http.services.ledgerline.loadbalancer.server.port=8000
|
2026-10-19 20:31:10 +03:30
|
|
|
# gateway now has two Traefik services, so each router names its own.
|
|
|
|
|
- traefik.http.routers.ledgerline.service=ledgerline
|
|
|
|
|
- traefik.http.routers.gateway-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/gateway`)
|
|
|
|
|
- traefik.http.routers.gateway-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.gateway-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.gateway-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.routers.gateway-metrics.service=gateway-metrics
|
|
|
|
|
- traefik.http.services.gateway-metrics.loadbalancer.server.port=9464
|
|
|
|
|
# Shared by every <service>-metrics router. Declared here because a
|
|
|
|
|
# middleware needs an enabled container to carry it.
|
|
|
|
|
- traefik.http.middlewares.metrics-auth.basicauth.usersfile=/etc/traefik/metrics-users
|
|
|
|
|
- traefik.http.middlewares.metrics-path.replacepath.path=/metrics
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
postgres:
|
|
|
|
|
ports: !reset []
|
|
|
|
|
|
|
|
|
|
redis:
|
|
|
|
|
ports: !reset []
|
|
|
|
|
|
|
|
|
|
ledger:
|
|
|
|
|
ports: !reset []
|
2026-10-19 20:31:10 +03:30
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.ledger-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/ledger`)
|
|
|
|
|
- traefik.http.routers.ledger-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.ledger-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.ledger-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.ledger-metrics.loadbalancer.server.port=9464
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
fraud:
|
|
|
|
|
ports: !reset []
|
2026-10-19 20:31:10 +03:30
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.fraud-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/fraud`)
|
|
|
|
|
- traefik.http.routers.fraud-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.fraud-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.fraud-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.fraud-metrics.loadbalancer.server.port=9464
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
sanctions:
|
|
|
|
|
ports: !reset []
|
2026-10-19 20:31:10 +03:30
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.sanctions-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/sanctions`)
|
|
|
|
|
- traefik.http.routers.sanctions-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.sanctions-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.sanctions-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.sanctions-metrics.loadbalancer.server.port=9464
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
insights:
|
|
|
|
|
ports: !reset []
|
2026-10-19 20:31:10 +03:30
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.insights-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/insights`)
|
|
|
|
|
- traefik.http.routers.insights-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.insights-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.insights-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.insights-metrics.loadbalancer.server.port=9464
|
2026-10-09 15:38:29 +03:30
|
|
|
|
|
|
|
|
notifier:
|
|
|
|
|
ports: !reset []
|
2026-10-19 20:31:10 +03:30
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.notifier-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/notifier`)
|
|
|
|
|
- traefik.http.routers.notifier-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.notifier-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.notifier-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.notifier-metrics.loadbalancer.server.port=9464
|
|
|
|
|
|
|
|
|
|
audit:
|
|
|
|
|
labels:
|
|
|
|
|
- traefik.enable=true
|
|
|
|
|
- traefik.http.routers.audit-metrics.rule=Host(`ledgerline.vione.cloud`) && Path(`/metrics/audit`)
|
|
|
|
|
- traefik.http.routers.audit-metrics.entrypoints=websecure
|
|
|
|
|
- traefik.http.routers.audit-metrics.tls.certresolver=le
|
|
|
|
|
- traefik.http.routers.audit-metrics.middlewares=metrics-auth,metrics-path
|
|
|
|
|
- traefik.http.services.audit-metrics.loadbalancer.server.port=9464
|
2026-10-09 15:38:29 +03:30
|
|
|
|
2026-10-12 13:24:38 +03:30
|
|
|
# Ships every container's stdout/stderr to the platform's Loki on falken
|
|
|
|
|
# (k8s/), labelled `container`. Credentials come from .env.observability,
|
|
|
|
|
# generated by k8s/create-secrets.sh and copied to the server once.
|
|
|
|
|
# Publishes no ports; memory-capped because czy has 1 GB of RAM. Steady
|
|
|
|
|
# state is ~70 MiB, but the initial backfill of every container's log
|
|
|
|
|
# history peaked at ~150 MiB in testing, hence the headroom.
|
|
|
|
|
alloy-agent:
|
|
|
|
|
image: grafana/alloy:v1.10.2
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
command:
|
|
|
|
|
- run
|
|
|
|
|
- --storage.path=/var/lib/alloy/data
|
|
|
|
|
- /etc/alloy/config.alloy
|
|
|
|
|
env_file: .env.observability
|
|
|
|
|
mem_limit: 256m
|
|
|
|
|
volumes:
|
|
|
|
|
- ./observability/alloy-agent/config.alloy:/etc/alloy/config.alloy:ro
|
|
|
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
|
|
|
- alloy-agent-data:/var/lib/alloy/data
|
|
|
|
|
|
2026-10-09 15:38:29 +03:30
|
|
|
volumes:
|
|
|
|
|
letsencrypt:
|
2026-10-12 13:24:38 +03:30
|
|
|
alloy-agent-data:
|