# ───────────────────────────────────────────────────────────────────── # Production docker-compose для CryptoWallet API. # # Что в этом stack: # - api — наш Node.js API (multi-stage build из ./Dockerfile) # - postgres — хранилище encrypted_mnemonic + audit_log # - keydb — Redis-compatible для idempotency cache + asset map cache # # Что НЕ включено (production не использует — оператор настраивает отдельно): # - Vault — production использует HashiCorp Vault HA cluster (VAULT_ADDR в .env) # - JWT signer— production принимает JWT от bitok (внешний сервис, JWT_ISSUER в .env) # - web-ui — фронтенд деплоится отдельно (Vercel / nginx CDN / etc.) # - vault-init — production Vault уже инициализирован оператором (см. README pre-deploy) # # Security hardening: # - api запускается под uid 1001, read-only fs, cap_drop ALL, no-new-privileges # - api порт 3001 биндится на 127.0.0.1 (наружу через nginx + TLS, оператор) # - postgres + keydb без exposed ports (только internal docker network) # - .env с secrets — 600 permission, не в репо # # Usage: # cp .env.example .env # заполнить все VAULT_*, JWT_*, REDIS_PASSWORD, CORS_ORIGINS, etc. # chmod 600 .env # docker compose up -d --build # docker compose logs -f api # ───────────────────────────────────────────────────────────────────── services: api: build: # Context = parent dir (мы внутри deployserver/, апи берёт apps/api/ из родителя) context: .. dockerfile: deployserver/Dockerfile image: cryptowallet-api:latest container_name: cw-api restart: unless-stopped depends_on: postgres: condition: service_healthy keydb: condition: service_healthy env_file: - .env environment: # Override DB/Redis host для docker network (если в .env стоят prod hosts — # внутри compose их replace на internal service names) POSTGRES_HOST: postgres POSTGRES_PORT: "5432" REDIS_HOST: keydb REDIS_PORT: "6379" ports: # Loopback only — наружу через nginx reverse proxy + TLS - "127.0.0.1:3001:3001" read_only: true tmpfs: - /tmp cap_drop: - ALL security_opt: - no-new-privileges:true deploy: resources: limits: memory: 512M cpus: "1.0" pids: 200 reservations: memory: 256M healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/health"] interval: 30s timeout: 5s retries: 3 start_period: 15s logging: driver: json-file options: max-size: "10m" max-file: "5" postgres: image: postgres:16-alpine container_name: cw-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER required} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required} POSTGRES_DB: ${POSTGRES_DB:-cryptowallet} volumes: - pgdata:/var/lib/postgresql/data # Bind-mount schema.sql если оператор хочет авто-init на свежей БД. # На existing — pg ignores 01-schema.sql (initdb запускается только если /var/lib/postgresql/data пуст). # См. README — лучше прогонять schema руками: psql -f cryptowallet-schema.sql # - ./cryptowallet-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro # Internal only — никаких ports на host expose: - "5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB:-cryptowallet}"] interval: 10s timeout: 3s retries: 5 logging: driver: json-file options: max-size: "10m" max-file: "5" keydb: image: eqalpha/keydb:alpine container_name: cw-keydb restart: unless-stopped volumes: - keydb_data:/data expose: - "6379" command: - keydb-server - --requirepass - "${REDIS_PASSWORD:?REDIS_PASSWORD required}" - --dir - /data - --appendonly - "yes" - --appendfsync - everysec - --save - "900" - "1" - --save - "300" - "10" - --save - "60" - "10000" - --maxmemory - "256mb" - --maxmemory-policy - "allkeys-lru" healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 3s retries: 5 deploy: resources: limits: memory: 320M logging: driver: json-file options: max-size: "10m" max-file: "5" volumes: pgdata: keydb_data: networks: default: driver: bridge