Docker compose con variabili d'ambiente

This commit is contained in:
Lorenzo Sanesi
2026-08-10 13:52:13 +02:00
parent f4afaf0792
commit 04ba3d541f
6 changed files with 147 additions and 50 deletions
+56
View File
@@ -0,0 +1,56 @@
# ==========================================================================
# ScommettiApp - variabili d'ambiente per docker-compose (root)
#
# Copia questo file in ".env" nella stessa cartella e compila i valori.
# Il file ".env" reale NON va committato (è già in .gitignore).
# ==========================================================================
# --------------------------------------------------------------------------
# Rete / porte pubblicate sull'host
# --------------------------------------------------------------------------
# IP su cui pubblicare le porte dei container sull'host della VPS.
# Caddy gira in un CT Proxmox separato (non nella stessa rete Docker), quindi
# deve raggiungere questi servizi via IP dell'host + porta pubblicata.
# Imposta qui l'IP con cui la VPS è raggiungibile dal CT di Caddy (es. IP
# della rete interna/bridge Proxmox), NON 0.0.0.0: pubblicare su 0.0.0.0
# espone i servizi anche a internet, bypassando Caddy (e occhio: le regole
# iptables di Docker bypassano ufw se non configurato apposta - vedi note
# nel Caddyfile.example).
BIND_IP=10.0.0.10
BACKEND_PORT=3000
FRONTEND_PORT=8081
KEYCLOAK_PORT=8080
# --------------------------------------------------------------------------
# Applicazione
# --------------------------------------------------------------------------
NODE_ENV=production
# --------------------------------------------------------------------------
# PostgreSQL (dati app: users, groups, friendships, ...)
# --------------------------------------------------------------------------
POSTGRES_DB=amici_scommesse
POSTGRES_USER=admin
POSTGRES_PASSWORD=change-me-strong-password
# --------------------------------------------------------------------------
# MongoDB (bets)
# --------------------------------------------------------------------------
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=change-me-strong-password
MONGO_INITDB_DATABASE=amici-scommesse
# --------------------------------------------------------------------------
# Keycloak
# --------------------------------------------------------------------------
# Dominio pubblico con cui Caddy espone l'app (usato da Keycloak per
# costruire URL assoluti corretti dietro il reverse proxy).
DOMAIN=scommetti.example.com
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=change-me-strong-password
KEYCLOAK_DB=keycloak
KEYCLOAK_DB_USER=keycloak
KEYCLOAK_DB_PASSWORD=change-me-strong-password
+1 -5
View File
@@ -19,10 +19,6 @@ module.exports = {
uri: uri:
process.env.MONGO_URI || process.env.MONGO_URI ||
'mongodb://localhost:27017/amici-scommesse', 'mongodb://localhost:27017/amici-scommesse',
options: { options: {},
user: 'root',
pass: 'root@servovio',
authSource: 'admin',
},
}, },
}; };
+1 -1
View File
@@ -7,7 +7,7 @@ class UsersController {
async getCurrentUser(req, res, next) { async getCurrentUser(req, res, next) {
try { try {
const user = await this.usersService.getUserById(req.userId); const user = await this.usersService.getCurrentUserWithRank(req.userId);
return ResponseHandler.success(res, user, 'User retrieved successfully'); return ResponseHandler.success(res, user, 'User retrieved successfully');
} catch (error) { } catch (error) {
next(error); next(error);
@@ -32,6 +32,27 @@ class UsersRepository {
} }
} }
async count() {
try {
return await this.User.count();
} catch (error) {
throw new DatabaseError('Failed to count users', error);
}
}
async countWithScoreAbove(score, excludeId) {
try {
return await this.User.count({
where: {
score: { [Op.gt]: score },
id: { [Op.ne]: excludeId },
},
});
} catch (error) {
throw new DatabaseError('Failed to count users above score', error);
}
}
async findById(id, options = {}) { async findById(id, options = {}) {
try { try {
const user = await this.User.findByPk(id, options); const user = await this.User.findByPk(id, options);
+11
View File
@@ -13,6 +13,17 @@ class UsersService {
return await this.usersRepository.findById(id); return await this.usersRepository.findById(id);
} }
async getCurrentUserWithRank(id) {
const user = await this.usersRepository.findById(id);
const userJson = user.toJSON ? user.toJSON() : { ...user };
const totalUsers = await this.usersRepository.count();
const usersAbove = await this.usersRepository.countWithScoreAbove(userJson.score, id);
const rankPercentile = totalUsers > 1
? Math.max(1, Math.round((usersAbove / totalUsers) * 100))
: 1;
return { ...userJson, rankPercentile };
}
async searchUsers(query, excludeUserId) { async searchUsers(query, excludeUserId) {
return await this.usersRepository.search(query, excludeUserId); return await this.usersRepository.search(query, excludeUserId);
} }
+57 -44
View File
@@ -1,37 +1,45 @@
version: '3.8' version: '3.8'
# Stack di produzione per la VPS, pensato per stare dietro a Caddy che gira
# su un CT Proxmox separato (routing per path sullo stesso dominio: "/" al
# frontend, "/api" al backend, "/auth" a Keycloak - vedi Caddyfile.example).
#
# Le variabili sono lette da un file ".env" nella stessa cartella
# (copia .env.example -> .env e compila i valori prima di avviare lo stack).
#
# Solo backend, frontend e keycloak pubblicano una porta sull'host (quella
# che deve raggiungere Caddy): postgres, mongodb e keycloak-db restano
# raggiungibili solo dalla rete Docker interna.
services: services:
# PostgreSQL Database # PostgreSQL Database (dati app)
postgres: postgres:
image: postgres:15-alpine image: postgres:15-alpine
container_name: scommetti-postgres container_name: scommetti-postgres
environment: environment:
POSTGRES_DB: amici_scommesse POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: admin POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: admin POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5432:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./backend-node/init.sql:/docker-entrypoint-initdb.d/init.sql - ./backend-node/init.sql:/docker-entrypoint-initdb.d/init.sql
networks: networks:
- scommetti-network - scommetti-network
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d amici_scommesse"] test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: unless-stopped
# MongoDB Database # MongoDB Database (bets)
mongodb: mongodb:
image: mongo:7 image: mongo:7
container_name: scommetti-mongodb container_name: scommetti-mongodb
environment: environment:
MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: root@servovio MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
MONGO_INITDB_DATABASE: amici-scommesse MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
ports:
- "27017:27017"
volumes: volumes:
- mongodb_data:/data/db - mongodb_data:/data/db
networks: networks:
@@ -41,43 +49,53 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: unless-stopped
# Keycloak Database # Keycloak Database
keycloak-db: keycloak-db:
image: postgres:15-alpine image: postgres:15-alpine
container_name: scommetti-keycloak-db container_name: scommetti-keycloak-db
environment: environment:
POSTGRES_DB: keycloak POSTGRES_DB: ${KEYCLOAK_DB}
POSTGRES_USER: keycloak POSTGRES_USER: ${KEYCLOAK_DB_USER}
POSTGRES_PASSWORD: keycloak POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
volumes: volumes:
- keycloak_db_data:/var/lib/postgresql/data - keycloak_db_data:/var/lib/postgresql/data
networks: networks:
- scommetti-network - scommetti-network
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"] test: ["CMD-SHELL", "pg_isready -U ${KEYCLOAK_DB_USER} -d ${KEYCLOAK_DB}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: unless-stopped
# Keycloak Auth Server # Keycloak Auth Server
keycloak: keycloak:
image: quay.io/keycloak/keycloak:23.0.0 image: quay.io/keycloak/keycloak:23.0.0
container_name: scommetti-keycloak container_name: scommetti-keycloak
command: start-dev --import-realm command: start --import-realm
environment: environment:
KC_DB: postgres KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak KC_DB_URL: jdbc:postgresql://keycloak-db:5432/${KEYCLOAK_DB}
KC_DB_USERNAME: keycloak KC_DB_USERNAME: ${KEYCLOAK_DB_USER}
KC_DB_PASSWORD: keycloak KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
KEYCLOAK_ADMIN_PASSWORD: admin KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HEALTH_ENABLED: "true" KC_HEALTH_ENABLED: "true"
KC_METRICS_ENABLED: "true" KC_METRICS_ENABLED: "true"
KC_HOSTNAME_STRICT: "false"
KC_HTTP_ENABLED: "true" KC_HTTP_ENABLED: "true"
# Keycloak è raggiunto tramite Caddy su https://${DOMAIN}/auth/*: gli
# diciamo di servire tutto sotto /auth e di fidarsi degli header
# X-Forwarded-* impostati dal proxy (TLS termina su Caddy, "edge").
KC_HTTP_RELATIVE_PATH: /auth
KC_HOSTNAME: ${DOMAIN}
KC_HOSTNAME_PATH: /auth
KC_HOSTNAME_STRICT: "true"
KC_HOSTNAME_STRICT_HTTPS: "true"
KC_PROXY: edge
ports: ports:
- "8080:8080" - "${BIND_IP}:${KEYCLOAK_PORT}:8080"
volumes: volumes:
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm.json:ro - ./keycloak/realm-export.json:/opt/keycloak/data/import/realm.json:ro
- ./keycloak/themes/scommettiapp:/opt/keycloak/themes/scommettiapp:ro - ./keycloak/themes/scommettiapp:/opt/keycloak/themes/scommettiapp:ro
@@ -87,29 +105,30 @@ services:
networks: networks:
- scommetti-network - scommetti-network
healthcheck: healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && printf 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && timeout 5 cat <&3 | grep -q 'UP'"] # Con KC_HTTP_RELATIVE_PATH=/auth l'health endpoint si sposta sotto
# /auth/health/ready (verificato: /health/ready da solo risponde 404).
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && printf 'GET /auth/health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && timeout 5 cat <&3 | grep -q 'UP'"]
interval: 15s interval: 15s
timeout: 15s timeout: 15s
retries: 10 retries: 10
start_period: 90s start_period: 90s
restart: unless-stopped
# Backend Node.js # Backend Node.js
backend: backend:
build: build:
context: ./backend-node context: ./backend-node
dockerfile: Dockerfile dockerfile: Dockerfile
target: production
container_name: scommetti-backend container_name: scommetti-backend
environment: environment:
NODE_ENV: development NODE_ENV: ${NODE_ENV}
PORT: 3000 PORT: 3000
POSTGRES_URI: postgres://admin:admin@postgres:5432/amici_scommesse POSTGRES_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
MONGO_URI: mongodb://mongodb:27017/amici-scommesse MONGO_URI: mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017/${MONGO_INITDB_DATABASE}?authSource=admin
KEYCLOAK_JWKS_URI: http://keycloak:8080/realms/scommettiapp/protocol/openid-connect/certs KEYCLOAK_JWKS_URI: http://keycloak:8080/auth/realms/scommettiapp/protocol/openid-connect/certs
ports: ports:
- "3000:3000" - "${BIND_IP}:${BACKEND_PORT}:3000"
volumes:
- ./backend-node:/app
- /app/node_modules
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@@ -119,23 +138,17 @@ services:
condition: service_healthy condition: service_healthy
networks: networks:
- scommetti-network - scommetti-network
command: npm start
restart: unless-stopped restart: unless-stopped
# Frontend Angular # Frontend Angular (build statica servita da nginx)
frontend: frontend:
build: build:
context: ./frontend-angular-new context: ./frontend-angular
dockerfile: Dockerfile dockerfile: Dockerfile
target: production
container_name: scommetti-frontend container_name: scommetti-frontend
environment:
NODE_ENV: development
ports: ports:
- "4200:4200" - "${BIND_IP}:${FRONTEND_PORT}:80"
volumes:
- ./frontend-angular-new:/app
- /app/node_modules
- /app/.angular
depends_on: depends_on:
- backend - backend
- keycloak - keycloak