docker-compose.yml•8.11 kB
version: '3.9'
x-app-common: &app-common
restart: unless-stopped
networks:
- secure-mcp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
# Main application
app:
<<: *app-common
build:
context: .
dockerfile: Dockerfile
target: production
cache_from:
- node:20.11.0-alpine3.19
image: secure-mcp-server:latest
container_name: secure-mcp-app
ports:
- "3000:3000"
environment:
NODE_ENV: ${NODE_ENV:-development}
DATABASE_URL: postgresql://mcp_user:${DB_PASSWORD:-SecurePass123!}@postgres:5432/mcp_db?schema=public&sslmode=require
REDIS_URL: redis://:${REDIS_PASSWORD:-RedisPass123!}@redis:6379/0
JWT_SECRET: ${JWT_SECRET:-development-secret-change-in-production}
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: ${VAULT_TOKEN:-development-token}
LOG_LEVEL: ${LOG_LEVEL:-info}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
RATE_LIMIT_WINDOW_MS: ${RATE_LIMIT_WINDOW_MS:-900000}
RATE_LIMIT_MAX_REQUESTS: ${RATE_LIMIT_MAX_REQUESTS:-100}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
vault:
condition: service_started
volumes:
- app-logs:/app/logs:rw
- app-tmp:/app/tmp:rw
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100M
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# PostgreSQL Database
postgres:
<<: *app-common
image: postgres:16.1-alpine3.19
container_name: secure-mcp-postgres
environment:
POSTGRES_DB: mcp_db
POSTGRES_USER: mcp_user
POSTGRES_PASSWORD: ${DB_PASSWORD:-SecurePass123!}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres-data:/var/lib/postgresql/data:rw
- ./scripts/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
ports:
- "5432:5432"
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- DAC_OVERRIDE
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mcp_user -d mcp_db"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
command: >
postgres
-c ssl=on
-c ssl_cert_file=/var/lib/postgresql/server.crt
-c ssl_key_file=/var/lib/postgresql/server.key
-c shared_preload_libraries=pg_stat_statements
-c pg_stat_statements.track=all
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c maintenance_work_mem=64MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
-c random_page_cost=1.1
-c effective_io_concurrency=200
-c work_mem=1310kB
-c min_wal_size=1GB
-c max_wal_size=4GB
# Redis Cache
redis:
<<: *app-common
image: redis:7.2.3-alpine3.19
container_name: secure-mcp-redis
command: >
redis-server
--requirepass ${REDIS_PASSWORD:-RedisPass123!}
--maxmemory 512mb
--maxmemory-policy allkeys-lru
--appendonly yes
--appendfsync everysec
--tcp-backlog 511
--tcp-keepalive 300
--timeout 0
--databases 16
--save 900 1
--save 300 10
--save 60 10000
--rdbcompression yes
--rdbchecksum yes
--stop-writes-on-bgsave-error yes
volumes:
- redis-data:/data:rw
ports:
- "6379:6379"
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETUID
- SETGID
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
# HashiCorp Vault for secrets management
vault:
<<: *app-common
image: hashicorp/vault:1.15.4
container_name: secure-mcp-vault
environment:
VAULT_DEV_ROOT_TOKEN_ID: ${VAULT_TOKEN:-development-token}
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
VAULT_LOG_LEVEL: ${VAULT_LOG_LEVEL:-info}
volumes:
- vault-data:/vault/data:rw
- vault-logs:/vault/logs:rw
- ./scripts/vault-config.hcl:/vault/config/vault.hcl:ro
ports:
- "8200:8200"
cap_drop:
- ALL
cap_add:
- IPC_LOCK
- SETUID
- SETGID
security_opt:
- no-new-privileges:true
command: server -dev
# Prometheus for metrics collection
prometheus:
<<: *app-common
image: prom/prometheus:v2.48.1
container_name: secure-mcp-prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus:rw
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=10GB'
- '--web.enable-lifecycle'
- '--web.enable-admin-api'
ports:
- "9090:9090"
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Grafana for visualization
grafana:
<<: *app-common
image: grafana/grafana:10.2.3
container_name: secure-mcp-grafana
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-GrafanaPass123!}
GF_INSTALL_PLUGINS: grafana-clock-panel,grafana-simple-json-datasource
GF_SERVER_ROOT_URL: ${GRAFANA_ROOT_URL:-http://localhost:3001}
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
volumes:
- grafana-data:/var/lib/grafana:rw
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro
ports:
- "3001:3000"
depends_on:
- prometheus
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
# Jaeger for distributed tracing
jaeger:
<<: *app-common
image: jaegertracing/all-in-one:1.53
container_name: secure-mcp-jaeger
environment:
COLLECTOR_OTLP_ENABLED: "true"
SPAN_STORAGE_TYPE: badger
BADGER_EPHEMERAL: "false"
BADGER_DIRECTORY_VALUE: /badger/data
BADGER_DIRECTORY_KEY: /badger/key
volumes:
- jaeger-data:/badger:rw
ports:
- "16686:16686" # Jaeger UI
- "4318:4318" # OTLP HTTP
- "4317:4317" # OTLP gRPC
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# NGINX as reverse proxy
nginx:
<<: *app-common
image: nginx:1.25.3-alpine3.18
container_name: secure-mcp-nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- nginx-cache:/var/cache/nginx:rw
- nginx-logs:/var/log/nginx:rw
ports:
- "80:80"
- "443:443"
depends_on:
- app
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
app-logs:
driver: local
app-tmp:
driver: local
postgres-data:
driver: local
redis-data:
driver: local
vault-data:
driver: local
vault-logs:
driver: local
prometheus-data:
driver: local
grafana-data:
driver: local
jaeger-data:
driver: local
nginx-cache:
driver: local
nginx-logs:
driver: local
networks:
secure-mcp:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1