LoVisual/backend/docker-compose.prod.yml
loki5512344 c57f851a8b
feat(accounts): outgoing mail over SMTP (Resend), email verification, fail-closed password reset
- mail/ module (lettre, ru/en templates), links built only from PUBLIC_BASE_URL
- migration 0006: accounts.email_verified_at, shared email_tokens table (verify + reset), existing accounts marked verified
- reset mailer no longer logs tokens; RESET_MAIL_MODE=log is dev-only and refused with COOKIE_SECURE=true; Disabled by default answers 503
- forgot-password and resend-verification do their work in a background task (no timing oracle)
- device linking requires a verified email; email_verified exposed via /me and gRPC
- gateway rate limits, SMTP_* in compose and .env.example
- frontend: verify, forgot-password, reset-password pages, verify banner, ru/en strings
2026-10-09 21:08:33 +02:00

152 lines
4.5 KiB
YAML

name: lovisual
networks:
lovisual-internal:
driver: bridge
volumes:
lovisual-pg-data:
lovisual-minio-data:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: lovisual
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
volumes:
- lovisual-pg-data:/var/lib/postgresql/data
- ./deploy/pg-init:/docker-entrypoint-initdb.d:ro
networks: [lovisual-internal]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lovisual"]
interval: 5s
timeout: 5s
retries: 10
minio:
image: quay.io/minio/minio:latest
restart: unless-stopped
command: server /data
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
volumes:
- lovisual-minio-data:/data
networks: [lovisual-internal]
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 10
minio-init:
image: quay.io/minio/minio:latest
depends_on:
minio:
condition: service_healthy
networks: [lovisual-internal]
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 $${S3_ACCESS_KEY} $${S3_SECRET_KEY} &&
(mc mb local/$${S3_BUCKET} || true) &&
echo bucket-ready
"
environment:
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
accounts-service:
build:
context: .
dockerfile: accounts-service/Dockerfile
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
minio-init:
condition: service_completed_successfully
env_file: .env
environment:
DATABASE_URL: postgres://lovisual:${POSTGRES_PASSWORD}@postgres:5432/accounts_db
S3_ENDPOINT: http://minio:9000
# Browser-facing avatar prefix. Served same-origin through the site's
# /api proxy -> gateway -> accounts-service, so MinIO stays private.
AVATAR_PUBLIC_BASE_URL: https://visual.loki-code.dev/api/media
PORT: 8081
GRPC_PORT: 50051
# Outgoing mail (Resend SMTP): secrets live in .env on the VPS, never
# in the repo. Empty SMTP_HOST keeps mail disabled (endpoints answer
# 503, fail-closed).
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
MAIL_FROM: ${MAIL_FROM:-}
# Link origin inside emails; must match the public site origin.
PUBLIC_BASE_URL: https://visual.loki-code.dev
MAIL_LANG: ${MAIL_LANG:-ru}
networks: [lovisual-internal]
configs-service:
build:
context: .
dockerfile: configs-service/Dockerfile
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
accounts-service:
condition: service_started
env_file: .env
environment:
CONFIGS_DATABASE_URL: postgres://lovisual:${POSTGRES_PASSWORD}@postgres:5432/configs_db
ACCOUNTS_GRPC_URL: http://accounts-service:50051
CONFIGS_PORT: 8082
networks: [lovisual-internal]
chat-service:
build:
context: .
dockerfile: chat-service/Dockerfile
restart: unless-stopped
env_file: .env
environment:
CHAT_PORT: 8083
networks: [lovisual-internal]
gateway:
build:
context: .
dockerfile: gateway/Dockerfile
restart: unless-stopped
depends_on:
accounts-service:
condition: service_started
configs-service:
condition: service_started
chat-service:
condition: service_started
env_file: .env
environment:
GATEWAY_PORT: 8080
ACCOUNTS_HTTP_URL: http://accounts-service:8081
ACCOUNTS_GRPC_URL: http://accounts-service:50051
CONFIGS_HTTP_URL: http://configs-service:8082
CHAT_HTTP_URL: http://chat-service:8083
# This stack is only ever exposed through nginx (see deploy/), so the
# rate limiter must read the client IP from X-Forwarded-For. Without
# this every client shares the proxy's socket address and one bucket:
# the global 300/min and login 5/min would be site-wide self-DoS.
# Keep TRUST_PROXY=false in .env for direct-exposure dev runs.
TRUST_PROXY: "true"
# Read-only static files served under GET /downloads/* (lovisual.jar).
DOWNLOADS_DIR: /srv/downloads
volumes:
- ./downloads:/srv/downloads:ro
ports:
- "127.0.0.1:8080:8080"
networks: [lovisual-internal]