fix(security): harden production Docker — bind DB/Redis to localhost, add Redis auth

- Postgres and Redis ports now bind to 127.0.0.1 only, preventing exposure
  to the network even if the host firewall has a gap
- Redis requires a password (REDIS_PASSWORD) via --requirepass; REDIS_URL in
  app and migrator services updated to include the credential
- Redis healthcheck updated to pass -a flag so it still works with auth enabled
- REDIS_PASSWORD added to .env.example with generation hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 21:41:15 +02:00
parent afabaa0b7a
commit 20fb39fd05
2 changed files with 12 additions and 6 deletions
+6
View File
@@ -29,6 +29,12 @@ DATABASE_URL=postgresql://capakraken:capakraken_dev@localhost:5433/capakraken
# ─── Redis ─────────────────────────────────────────────────────────────────── # ─── Redis ───────────────────────────────────────────────────────────────────
# REQUIRED in production — password for the Redis server.
# The Docker Compose prod stack passes this both to the redis-server process
# (--requirepass) and to the application via REDIS_URL.
# Generate one with: openssl rand -hex 32
REDIS_PASSWORD=
# REQUIRED for SSE (real-time updates) and rate limiting. # REQUIRED for SSE (real-time updates) and rate limiting.
# When using Docker Compose this is handled automatically inside the container # When using Docker Compose this is handled automatically inside the container
# (redis://redis:6379). Only needed when running `pnpm dev` directly on the host. # (redis://redis:6379). Only needed when running `pnpm dev` directly on the host.
+6 -6
View File
@@ -5,7 +5,7 @@ services:
image: postgres:16-alpine image: postgres:16-alpine
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${POSTGRES_PORT:-5432}:5432" - "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
environment: environment:
POSTGRES_DB: capakraken POSTGRES_DB: capakraken
POSTGRES_USER: capakraken POSTGRES_USER: capakraken
@@ -31,12 +31,12 @@ services:
image: redis:7-alpine image: redis:7-alpine
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${REDIS_PORT:-6379}:6379" - "127.0.0.1:${REDIS_PORT:-6379}:6379"
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru --requirepass ${REDIS_PASSWORD}
volumes: volumes:
- capakraken_prod_redis:/data - capakraken_prod_redis:/data
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "--no-auth-warning", "ping"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -50,7 +50,7 @@ services:
- .env.production - .env.production
environment: environment:
DATABASE_URL: postgresql://capakraken:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/capakraken DATABASE_URL: postgresql://capakraken:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/capakraken
REDIS_URL: redis://redis:6379 REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
RATE_LIMIT_BACKEND: ${RATE_LIMIT_BACKEND:-redis} RATE_LIMIT_BACKEND: ${RATE_LIMIT_BACKEND:-redis}
depends_on: depends_on:
postgres: postgres:
@@ -68,7 +68,7 @@ services:
- .env.production - .env.production
environment: environment:
DATABASE_URL: postgresql://capakraken:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/capakraken DATABASE_URL: postgresql://capakraken:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/capakraken
REDIS_URL: redis://redis:6379 REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
RATE_LIMIT_BACKEND: ${RATE_LIMIT_BACKEND:-redis} RATE_LIMIT_BACKEND: ${RATE_LIMIT_BACKEND:-redis}
depends_on: depends_on:
postgres: postgres: