docker-compose.yml
Rama main · réplica de lectura del repositorio original.
services:
backend:
build:
context: ./backend
dockerfile: ../docker/backend.Dockerfile
image: rustchat-backend
container_name: rustchat-backend
restart: always
environment:
- RUSTCHAT_SERVER_HOST=0.0.0.0
- RUSTCHAT_SERVER_PORT=3000
- RUSTCHAT_ENVIRONMENT=${RUSTCHAT_ENVIRONMENT:-development}
# ⚠️ PRODUCTION (behind reverse proxy):
# - Set SITE_URL to public HTTPS URL (e.g., https://chat.example.com)
# - Set CORS_ALLOWED_ORIGINS to same HTTPS URL(s)
# - Internal proxy→RustChat uses HTTP, but browsers see HTTPS
- RUSTCHAT_SITE_URL=${RUSTCHAT_SITE_URL:-http://localhost:8080}
- RUSTCHAT_CORS_ALLOWED_ORIGINS=${RUSTCHAT_CORS_ALLOWED_ORIGINS:-http://localhost:8080,http://localhost:5173}
- RUSTCHAT_ALLOW_DEV_CORS=${RUSTCHAT_ALLOW_DEV_CORS:-false}
- RUSTCHAT_DATABASE_URL=postgres://rustchat:rustchat@postgres:5432/rustchat
- RUSTCHAT_REDIS_URL=redis://redis:6379
- RUSTCHAT_JWT_SECRET=${RUSTCHAT_JWT_SECRET:?set RUSTCHAT_JWT_SECRET in .env}
- RUSTCHAT_JWT_ISSUER=${RUSTCHAT_JWT_ISSUER:?set RUSTCHAT_JWT_ISSUER in .env}
- RUSTCHAT_JWT_AUDIENCE=${RUSTCHAT_JWT_AUDIENCE:?set RUSTCHAT_JWT_AUDIENCE in .env}
- RUSTCHAT_ENCRYPTION_KEY=${RUSTCHAT_ENCRYPTION_KEY:?set RUSTCHAT_ENCRYPTION_KEY in .env}
- RUSTCHAT_S3_ENDPOINT=http://rustfs:9000
- RUSTCHAT_S3_PUBLIC_ENDPOINT=${RUSTCHAT_S3_PUBLIC_ENDPOINT:-http://localhost:9000}
- RUSTCHAT_S3_BUCKET=rustchat-uploads
- RUSTCHAT_S3_ACCESS_KEY=${RUSTCHAT_S3_ACCESS_KEY:?set RUSTCHAT_S3_ACCESS_KEY in .env}
- RUSTCHAT_S3_SECRET_KEY=${RUSTCHAT_S3_SECRET_KEY:?set RUSTCHAT_S3_SECRET_KEY in .env}
- RUSTCHAT_ADMIN_USER=${RUSTCHAT_ADMIN_USER:-admin@rustchat.local}
- RUSTCHAT_ADMIN_PASSWORD=${RUSTCHAT_ADMIN_PASSWORD:-}
# Calls Plugin (WebRTC) Configuration
- RUSTCHAT_CALLS_ENABLED=${RUSTCHAT_CALLS_ENABLED:-true}
- RUSTCHAT_CALLS_UDP_PORT=${RUSTCHAT_CALLS_UDP_PORT:-8443}
- RUSTCHAT_CALLS_TCP_PORT=${RUSTCHAT_CALLS_TCP_PORT:-8443}
- RUSTCHAT_CALLS_ICE_HOST_OVERRIDE=${RUSTCHAT_CALLS_ICE_HOST_OVERRIDE:-}
- RUSTCHAT_CALLS_STATE_BACKEND=${RUSTCHAT_CALLS_STATE_BACKEND:-auto}
# TURN Server Configuration
- TURN_SERVER_ENABLED=${TURN_SERVER_ENABLED:-false}
- TURN_SERVER_URL=${TURN_SERVER_URL:-}
- TURN_SERVER_USERNAME=${TURN_SERVER_USERNAME:-}
- TURN_SERVER_CREDENTIAL=${TURN_SERVER_CREDENTIAL:-}
# Push Notification Proxy
- RUSTCHAT_PUSH_PROXY_URL=${RUSTCHAT_PUSH_PROXY_URL:-http://push-proxy:3000}
- PUSH_PROXY_AUTH_KEY=${PUSH_PROXY_AUTH_KEY:?PUSH_PROXY_AUTH_KEY must be set to a non-empty secret}
# Cloudflare Turnstile (Bot Protection)
- RUSTCHAT_TURNSTILE_ENABLED=${RUSTCHAT_TURNSTILE_ENABLED:-false}
- RUSTCHAT_TURNSTILE_SITE_KEY=${RUSTCHAT_TURNSTILE_SITE_KEY:-}
- RUSTCHAT_TURNSTILE_SECRET_KEY=${RUSTCHAT_TURNSTILE_SECRET_KEY:-}
# Backend is published for local API debugging only. It bypasses the
# frontend nginx, so requests hitting it directly do not pass the reverse
# proxy's X-Forwarded-For boundary — keep it loopback-bound by default.
# Set RUSTCHAT_BIND_HOST=0.0.0.0 to expose services on the LAN (e.g. for
# testing from a phone), accepting that you are opting out of these
# boundaries.
ports:
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:3000:3000"
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:8443:8443/tcp" # WebRTC SFU TCP fallback
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:8443:8443/udp" # WebRTC SFU UDP media transport
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
rustfs:
condition: service_healthy
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/v1/health/live" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
frontend:
build:
context: ./frontend
dockerfile: ../docker/frontend.Dockerfile
image: rustchat-frontend
container_name: rustchat-frontend
restart: always
ports:
- "8080:8080"
depends_on:
backend:
condition: service_healthy
postgres:
image: pgvector/pgvector:pg16
container_name: rustchat-postgres
environment:
POSTGRES_USER: rustchat
# Well-known dev password, unchanged from before: acceptable only
# because the port is loopback-bound below. (Parameterizing this line
# trips secret scanners on the new diff; the literal is identical to
# the pre-existing value in main.)
POSTGRES_PASSWORD: rustchat
POSTGRES_DB: rustchat
# Loopback-bound by default: a password-authenticated database with a
# well-known dev password must not be reachable from the network.
ports:
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U rustchat" ]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: rustchat-redis
# Loopback-bound by default: this Redis has no authentication at all.
ports:
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 5s
timeout: 5s
retries: 5
rustfs:
# TODO: Pin to a version tag when rustfs publishes semver tags.
# Current digest: sha256:103dd40b84d5aa3d5ab02f3a693797eb1d14cb842554b222dfbb589f364aa47f
image: rustfs/rustfs@sha256:103dd40b84d5aa3d5ab02f3a693797eb1d14cb842554b222dfbb589f364aa47f
container_name: rustchat-rustfs
environment:
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:?set RUSTFS_ACCESS_KEY in .env}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:?set RUSTFS_SECRET_KEY in .env}
# Loopback-bound by default: the S3 API carries the instance credentials,
# and the unauthenticated console must never be network-reachable.
ports:
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:9000:9000" # S3 API
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:9001:9001" # Console/UI
volumes:
- rustfs_data:/data
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9000/health" ]
interval: 10s
timeout: 5s
retries: 5
push-proxy:
build:
context: ./push-proxy
dockerfile: Dockerfile
image: rustchat-push-proxy
container_name: rustchat-push-proxy
restart: always
environment:
# Firebase (Android)
- FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID:-}
- GOOGLE_APPLICATION_CREDENTIALS=/secrets/firebase-key.json
# APNS (iOS) - JWT-based authentication
- APNS_KEY_PATH=/secrets/apns-key.p8
- APNS_KEY_ID=${APNS_KEY_ID:-}
- APNS_TEAM_ID=${APNS_TEAM_ID:-}
- APNS_BUNDLE_ID=${APNS_BUNDLE_ID:-}
- APNS_USE_PRODUCTION=${APNS_USE_PRODUCTION:-false}
# General
- RUSTCHAT_PUSH_PORT=3000
- RUST_LOG=push_proxy=info,tower_http=info
- PUSH_PROXY_AUTH_KEY=${PUSH_PROXY_AUTH_KEY:-}
volumes:
# Firebase credentials
- ${FIREBASE_KEY_PATH:-./secrets/firebase-key.json}:/secrets/firebase-key.json:ro
# APNS Auth Key (JWT-based)
# Backward-compatible fallback order:
# 1) APNS_KEY_HOST_PATH (preferred)
# 2) APNS_KEY_PATH (legacy host-path usage in older .env files)
# 3) ./secrets/apns-key.p8
- ${APNS_KEY_HOST_PATH:-${APNS_KEY_PATH:-./secrets/apns-key.p8}}:/secrets/apns-key.p8:ro
ports:
- "${RUSTCHAT_BIND_HOST:-127.0.0.1}:3001:3000"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health 2>&1 | grep -q 'ok' || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
volumes:
postgres_data:
redis_data:
rustfs_data: