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: Dockerfile target: accounts-service 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: Dockerfile target: configs-service 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: Dockerfile target: chat-service restart: unless-stopped env_file: .env environment: CHAT_PORT: 8083 networks: [lovisual-internal] gateway: build: context: . dockerfile: Dockerfile target: gateway 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]