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
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 44s
This commit is contained in:
22
.env.example
22
.env.example
@@ -1,24 +1,34 @@
|
|||||||
# Server Settings
|
# Server Settings
|
||||||
PORT=8080
|
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
|
DB_DSN=root:secret@tcp(127.0.0.1:3306)/emly?parseTime=true&loc=UTC
|
||||||
MAX_OPEN_CONNS=25
|
DB_MAX_OPEN_CONNS=25
|
||||||
MAX_IDLE_CONNS=5
|
DB_MAX_IDLE_CONNS=5
|
||||||
CONN_MAX_LIFETIME=5m
|
DB_CONN_MAX_LIFETIME=5
|
||||||
DATABASE_NAME=emly
|
DATABASE_NAME=emly
|
||||||
|
|
||||||
# API Keys
|
# API Keys
|
||||||
API_KEY=key-one
|
API_KEY=key-one
|
||||||
ADMIN_KEY=admin-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_MAX_REQS=10
|
||||||
RL_UNAUTH_WINDOW=5m
|
RL_UNAUTH_WINDOW=5m
|
||||||
RL_UNAUTH_MAX_FAILS=5
|
RL_UNAUTH_MAX_FAILS=5
|
||||||
RL_UNAUTH_BAN_DUR=15m
|
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_MAX_REQS=100
|
||||||
RL_AUTH_WINDOW=1m
|
RL_AUTH_WINDOW=1m
|
||||||
RL_AUTH_MAX_FAILS=20
|
RL_AUTH_MAX_FAILS=20
|
||||||
|
|||||||
154
docker-compose-prod.yml
Normal file
154
docker-compose-prod.yml
Normal 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
|
||||||
5
main.go
5
main.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@@ -23,6 +24,10 @@ func main() {
|
|||||||
// Load .env (ignored if not present in production)
|
// Load .env (ignored if not present in production)
|
||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
|
|
||||||
|
if name := os.Getenv("INSTANCE_NAME"); name != "" {
|
||||||
|
log.SetPrefix("[" + name + "] ")
|
||||||
|
}
|
||||||
|
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
db, err := database.Connect(cfg)
|
db, err := database.Connect(cfg)
|
||||||
|
|||||||
Reference in New Issue
Block a user