add Docker Compose configuration for Traefik and MySQL, update environment variables
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 44s

This commit is contained in:
Flavio Fois
2026-03-25 12:14:35 +01:00
parent 858b0642d9
commit 09a760e025
3 changed files with 175 additions and 6 deletions

View File

@@ -1,24 +1,34 @@
# Server Settings
PORT=8080
# DB Settings
# Infrastruttura Docker (Traefik + MySQL)
API_DOMAIN=api.esempio.com
ACME_EMAIL=tua@email.com
MYSQL_ROOT_PASSWORD=password-sicura
# DB Settings (usato per sviluppo locale; in Docker il DSN è costruito dal compose)
DB_DSN=root:secret@tcp(127.0.0.1:3306)/emly?parseTime=true&loc=UTC
MAX_OPEN_CONNS=25
MAX_IDLE_CONNS=5
CONN_MAX_LIFETIME=5m
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
DB_CONN_MAX_LIFETIME=5
DATABASE_NAME=emly
# API Keys
API_KEY=key-one
ADMIN_KEY=admin-key-one
# Rate Limiting (unauthenticated: no X-API-Key / X-Admin-Key)
# Rate Limiting — Traefik edge (condiviso tra repliche)
TRAEFIK_RL_AVERAGE=30
TRAEFIK_RL_BURST=10
TRAEFIK_RL_PERIOD=1m
# Rate Limiting — App (unauthenticated: no X-API-Key / X-Admin-Key)
RL_UNAUTH_MAX_REQS=10
RL_UNAUTH_WINDOW=5m
RL_UNAUTH_MAX_FAILS=5
RL_UNAUTH_BAN_DUR=15m
# Rate Limiting (authenticated: X-API-Key or X-Admin-Key present)
# Rate Limiting — App (authenticated: X-API-Key or X-Admin-Key present)
RL_AUTH_MAX_REQS=100
RL_AUTH_WINDOW=1m
RL_AUTH_MAX_FAILS=20

154
docker-compose-prod.yml Normal file
View File

@@ -0,0 +1,154 @@
networks:
traefik_public:
driver: bridge
internal:
driver: bridge
internal: true
volumes:
mysql_data:
traefik_certs:
logs:
# ── Anchor: variabili d'ambiente comuni a tutte le repliche ─────────────────
x-api-env: &api-env
PORT: "8080"
DB_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/${DATABASE_NAME}?parseTime=true&loc=UTC"
DATABASE_NAME: ${DATABASE_NAME}
API_KEY: ${API_KEY}
ADMIN_KEY: ${ADMIN_KEY}
DB_MAX_OPEN_CONNS: ${DB_MAX_OPEN_CONNS:-25}
DB_MAX_IDLE_CONNS: ${DB_MAX_IDLE_CONNS:-5}
DB_CONN_MAX_LIFETIME: ${DB_CONN_MAX_LIFETIME:-5}
RL_UNAUTH_MAX_REQS: ${RL_UNAUTH_MAX_REQS:-10}
RL_UNAUTH_WINDOW: ${RL_UNAUTH_WINDOW:-5m}
RL_UNAUTH_MAX_FAILS: ${RL_UNAUTH_MAX_FAILS:-5}
RL_UNAUTH_BAN_DUR: ${RL_UNAUTH_BAN_DUR:-15m}
RL_AUTH_MAX_REQS: ${RL_AUTH_MAX_REQS:-100}
RL_AUTH_WINDOW: ${RL_AUTH_WINDOW:-1m}
RL_AUTH_MAX_FAILS: ${RL_AUTH_MAX_FAILS:-20}
RL_AUTH_BAN_DUR: ${RL_AUTH_BAN_DUR:-5m}
# ── Anchor: configurazione base del servizio API ────────────────────────────
x-api-base: &api-base
build: .
restart: unless-stopped
networks:
- traefik_public
- internal
volumes:
- logs:/logs
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
depends_on:
mysql:
condition: service_healthy
labels:
# Traefik: abilita il container e definisce il router HTTPS
- "traefik.enable=true"
- "traefik.http.routers.emly-api.rule=Host(`${API_DOMAIN}`)"
- "traefik.http.routers.emly-api.entrypoints=websecure"
- "traefik.http.routers.emly-api.tls.certresolver=letsencrypt"
- "traefik.http.routers.emly-api.middlewares=rl,hsts"
# Load balancer: tutte le repliche condividono lo stesso service name
- "traefik.http.services.emly-api.loadbalancer.server.port=8080"
- "traefik.http.services.emly-api.loadbalancer.healthcheck.path=/v1/health"
- "traefik.http.services.emly-api.loadbalancer.healthcheck.interval=10s"
- "traefik.http.services.emly-api.loadbalancer.healthcheck.timeout=3s"
# Rate limiting edge (condiviso tra repliche, applicato prima del LB)
- "traefik.http.middlewares.rl.ratelimit.average=${TRAEFIK_RL_AVERAGE:-30}"
- "traefik.http.middlewares.rl.ratelimit.burst=${TRAEFIK_RL_BURST:-10}"
- "traefik.http.middlewares.rl.ratelimit.period=${TRAEFIK_RL_PERIOD:-1m}"
# HSTS
- "traefik.http.middlewares.hsts.headers.stsSeconds=31536000"
- "traefik.http.middlewares.hsts.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.hsts.headers.forceSTSHeader=true"
# Watchtower: aggiorna automaticamente questa immagine
- "com.centurylinklabs.watchtower.enable=true"
# ── Servizi ─────────────────────────────────────────────────────────────────
services:
# ── Traefik ──────────────────────────────────────────────────────────────
traefik:
image: traefik:v3
restart: unless-stopped
command:
- "--api=false"
# Entry points
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
# Docker provider
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik_public"
# ACME / Let's Encrypt (TLS challenge)
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/certificates/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=INFO"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_certs:/certificates
networks:
- traefik_public
# ── MySQL ────────────────────────────────────────────────────────────────
mysql:
image: mysql:8
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${DATABASE_NAME}
volumes:
- mysql_data:/var/lib/mysql
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -p${MYSQL_ROOT_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# ── API replica 1 ────────────────────────────────────────────────────────
api-1:
<<: *api-base
environment:
<<: *api-env
INSTANCE_NAME: api-1
# ── API replica 2 ────────────────────────────────────────────────────────
api-2:
<<: *api-base
environment:
<<: *api-env
INSTANCE_NAME: api-2
# ── API replica 3 ────────────────────────────────────────────────────────
api-3:
<<: *api-base
environment:
<<: *api-env
INSTANCE_NAME: api-3
# ── Watchtower ───────────────────────────────────────────────────────────
watchtower:
image: containrrr/watchtower
restart: unless-stopped
command:
- "--cleanup"
- "--schedule"
- "0 0 * * * *"
environment:
WATCHTOWER_LABEL_ENABLE: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/go-chi/chi/v5"
@@ -23,6 +24,10 @@ func main() {
// Load .env (ignored if not present in production)
_ = godotenv.Load()
if name := os.Getenv("INSTANCE_NAME"); name != "" {
log.SetPrefix("[" + name + "] ")
}
cfg := config.Load()
db, err := database.Connect(cfg)