Files
SpecterOps-Nemesis/compose.yaml
T
Lee Chagolla-Christensen 6099a6515b update dapr to 1.16.1
2025-10-10 17:32:12 -07:00

1028 lines
36 KiB
YAML

name: nemesis
networks:
nemesis:
driver: bridge
volumes:
grafana_data:
jaeger_data:
loki_data:
minio_data:
phoenix_data:
postgres_data:
prometheus_data:
rabbitmq_data:
empty:
empty-mounted-containers:
services:
########################################
# Enrichment Services + Dapr sidecars
########################################
web-api:
image: ghcr.io/specterops/nemesis/web-api:latest
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8000/healthz"]
interval: 10s
timeout: 5s
retries: 5
environment:
- APP_ID=web-api
- DAPR_GRPC_PORT=50001
- DAPR_HTTP_PORT=3500
- DEFAULT_EXPIRATION_DAYS=${DEFAULT_EXPIRATION_DAYS:-100}
volumes:
- ${MOUNTED_CONTAINER_PATH:-empty-mounted-containers}:/mounted-containers
logging: &logging-config
driver: "json-file"
options: { max-size: "10m", max-file: "3" }
depends_on:
postgres: { condition: service_started }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
labels:
- "traefik.enable=true"
- "traefik.http.routers.web-api.rule=PathPrefix(`/api`)"
- "traefik.http.services.web-api.loadbalancer.server.port=8000"
- "traefik.http.routers.web-api.entrypoints=websecure"
- "traefik.http.routers.web-api.tls=true"
- "traefik.http.routers.web-api.middlewares=auth"
web-api-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--app-id",
"web-api",
"--max-body-size",
"1Gi",
"--app-port",
"8000",
"--dapr-http-port",
"3500",
"--dapr-grpc-port",
"50001",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:?}
- MINIO_ROOT_USER=${MINIO_ROOT_USER:?}
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
- POSTGRES_CONNECTION_STRING=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment?sslmode=disable
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD:?}
- RABBITMQ_USER=${RABBITMQ_USER:?}
depends_on:
web-api: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:web-api"
noseyparker-scanner:
image: ghcr.io/specterops/nemesis/noseyparker-scanner:latest
depends_on:
minio: { condition: service_healthy }
rabbitmq: { condition: service_healthy }
environment:
- DAPR_GRPC_PORT=50013
- DAPR_PORT=3513
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER:?}
- MINIO_BUCKET=files
- MINIO_ENDPOINT=http://minio:9000
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD:?}
- RUST_LOG=info
- SNIPPET_LENGTH=512 # context length around any Nosey Parker matches
- MAX_CONCURRENT_FILES=2 # maximum number of concurrent files to scan
- MAX_FILE_SIZE_MB=200 # maximum file size to scan
- DECOMPRESS_ZIPS=true # whether to decompress+scan zips
- MAX_EXTRACT_SIZE_MB=1000 # maximum number of bytes to extract if decompressing
volumes:
- ./projects/noseyparker_scanner/custom_rules/:/opt/noseyparker:ro
noseyparker-scanner-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"noseyparker-scanner",
"--app-port",
"50042",
"--app-protocol",
"grpc",
"--dapr-http-port",
"3513",
"--dapr-grpc-port",
"50013",
"--placement-host-address",
"placement:50006",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--app-max-concurrency",
"1",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
environment:
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
depends_on: [noseyparker-scanner, placement]
network_mode: "service:noseyparker-scanner"
dotnet-service:
image: ghcr.io/specterops/nemesis/dotnet-service:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
minio: { condition: service_healthy }
rabbitmq: { condition: service_healthy }
environment:
- DAPR_GRPC_PORT=50014
- DAPR_PORT=3514
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER:?}
- MINIO_BUCKET=files
- MINIO_ENDPOINT=http://minio:9000
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD:?}
- ASPNETCORE_URLS=http://0.0.0.0:5000
expose:
- "5000"
dotnet-service-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"dotnet-service",
"--app-port",
"5000",
"--app-protocol",
"http",
"--dapr-http-port",
"3514",
"--dapr-grpc-port",
"50014",
"--placement-host-address",
"placement:50006",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--app-max-concurrency",
"1",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
environment:
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
depends_on: [dotnet-service, placement]
network_mode: "service:dotnet-service"
file-enrichment:
image: ghcr.io/specterops/nemesis/file-enrichment:latest
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8001/healthz"]
interval: 10s
timeout: 5s
retries: 5
volumes: []
# Uncomment the following line to use custom YARA rules
# - ./libs/file_enrichment_modules/yara_rules/prod/:/yara_rules/:ro
environment:
- APP_ID=file-enrichment
- DAPR_GRPC_PORT=50003
- DAPR_HTTP_PORT=3503
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- MAX_PARALLEL_ENRICHMENT_MODULES=${MAX_PARALLEL_ENRICHMENT_MODULES:-5}
- MAX_PARALLEL_WORKFLOWS=${MAX_PARALLEL_WORKFLOWS:-5}
- MAX_WORKFLOW_EXECUTION_TIME=${MAX_WORKFLOW_EXECUTION_TIME:-300}
- NEMESIS_MONITORING=${NEMESIS_MONITORING:-disabled}
- NEMESIS_URL=${NEMESIS_URL:?}
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel-collector:4317
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_INSECURE=true
- RIGGING_GENERATOR_CREDENTIALS=${RIGGING_GENERATOR_CREDENTIALS:-}
- RIGGING_GENERATOR_SUMMARY=${RIGGING_GENERATOR_SUMMARY:-}
- RIGGING_GENERATOR_TRIAGE=${RIGGING_GENERATOR_TRIAGE:-}
- UVICORN_WORKERS=${UVICORN_WORKERS:-2}
- WORKFLOW_RUNTIME_LOG_LEVEL=${WORKFLOW_RUNTIME_LOG_LEVEL:-WARNING}
- WORKFLOW_CLIENT_LOG_LEVEL=${WORKFLOW_CLIENT_LOG_LEVEL:-WARNING}
logging: *logging-config
depends_on:
postgres: { condition: service_started }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
file-enrichment-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"file-enrichment",
"--app-port",
"8001",
"--dapr-http-port",
"3503",
"--dapr-grpc-port",
"50003",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/components/workflowstate.yaml:/dapr/components/workflowstate.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- POSTGRES_CONNECTION_STRING=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment?sslmode=disable
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
- MINIO_ROOT_USER=${MINIO_ROOT_USER:?}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:?}
depends_on:
file-enrichment: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:file-enrichment"
frontend:
image: ghcr.io/specterops/nemesis/frontend:latest
depends_on: [postgres, hasura]
environment:
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
command: >
sh -c "
SECRET=\"$$HASURA_ADMIN_SECRET\" &&
sed -i \"s/\\$$HASURA_ADMIN_SECRET/$$SECRET/g\" /usr/share/nginx/html/index.html &&
nginx -g 'daemon off;'
"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000"]
interval: 10s
timeout: 10s
retries: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=PathPrefix(`/`)"
- "traefik.http.services.frontend.loadbalancer.server.port=3000"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls=true"
- "traefik.http.routers.frontend.middlewares=auth"
jupyter:
profiles: ["jupyter"]
image: ghcr.io/specterops/nemesis/jupyter:latest
depends_on: [postgres, hasura]
environment:
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
- HASURA_GRAPHQL_URL=http://hasura:8080/v1/graphql
- JUPYTER_PASSWORD=${JUPYTER_PASSWORD:-}
volumes:
- "./projects/jupyter/notebooks:/home/jovyan/work"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8888/jupyter/api"]
interval: 30s
timeout: 10s
retries: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.jupyter.rule=PathPrefix(`/jupyter`)"
- "traefik.http.services.jupyter.loadbalancer.server.port=8888"
- "traefik.http.routers.jupyter.entrypoints=websecure"
- "traefik.http.routers.jupyter.tls=true"
- "traefik.http.routers.jupyter.middlewares=auth"
alerting:
image: ghcr.io/specterops/nemesis/alerting:latest
environment:
- DAPR_HTTP_PORT=3508
- DAPR_GRPC_PORT=50008
- APPRISE_URLS=${APPRISE_URLS:-}
- NEMESIS_URL=${NEMESIS_URL:?}
logging: *logging-config
depends_on:
postgres: { condition: service_healthy }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
hasura: { condition: service_healthy }
healthcheck: &healthcheck-python-svc
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8000/healthz"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
alerting-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"alerting",
"--app-port",
"8000",
"--dapr-http-port",
"3508",
"--dapr-grpc-port",
"50008",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
depends_on:
alerting: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:alerting"
agents:
image: ghcr.io/specterops/nemesis/agents:latest
environment:
- DAPR_HTTP_PORT=3509
- DAPR_GRPC_PORT=50009
- NEMESIS_URL=${NEMESIS_URL:?}
- WORKFLOW_RUNTIME_LOG_LEVEL=${WORKFLOW_RUNTIME_LOG_LEVEL:-WARNING}
- WORKFLOW_CLIENT_LOG_LEVEL=${WORKFLOW_CLIENT_LOG_LEVEL:-WARNING}
- LITELLM_ADMIN_KEY=sk-${LITELLM_ADMIN_PASSWORD:-admin123}
- MAX_BUDGET=${LLM_MAX_BUDGET:-100}
- BUDGET_DURATION=${LLM_BUDGET_DURATION:-30d}
- DOTNET_ANALYSIS_RUN_REQUEST_LIMIT=${DOTNET_ANALYSIS_RUN_REQUEST_LIMIT:-25} # max LLM calls to use per .NET program analysis run
- DOTNET_ANALYSIS_RUN_TOKENS_LIMIT=${DOTNET_ANALYSIS_RUN_TOKENS_LIMIT:-1000000} # max tokens to use per .NET program analysis run
# if we hit this number of the same triage values for the same file, all future findings get that value
- TRIAGE_CONSENSUS_THRESHOLD=${TRIAGE_CONSENSUS_THRESHOLD:-3}
# Phoenix LLM tracing configuration
- PHOENIX_ENABLED=${PHOENIX_ENABLED:-false}
- PHOENIX_ENDPOINT=${PHOENIX_ENDPOINT:-http://phoenix:6006/v1/traces}
- PHOENIX_SQL_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/phoenix
- NEMESIS_MONITORING=${NEMESIS_MONITORING:-disabled}
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel-collector:4317
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_INSECURE=true
logging: *logging-config
depends_on:
postgres: { condition: service_healthy }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
hasura: { condition: service_healthy }
healthcheck: *healthcheck-python-svc
agents-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"agents",
"--app-port",
"8000",
"--dapr-http-port",
"3509",
"--dapr-grpc-port",
"50009",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/components/workflowstate.yaml:/dapr/components/workflowstate.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- POSTGRES_CONNECTION_STRING=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment?sslmode=disable
- POSTGRES_CONNECTION_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
- MINIO_ROOT_USER=${MINIO_ROOT_USER:?}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:?}
depends_on:
agents: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:agents"
housekeeping:
image: ghcr.io/specterops/nemesis/housekeeping:latest
environment:
- APP_ID=housekeeping
- CLEANUP_SCHEDULE=*/3 * * * * # Every 3 minutes
- DAPR_GRPC_PORT=50011
- DAPR_HTTP_PORT=3510
logging: *logging-config
depends_on:
postgres: { condition: service_healthy }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
healthcheck: *healthcheck-python-svc
housekeeping-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"housekeeping",
"--app-port",
"8000",
"--dapr-http-port",
"3510",
"--dapr-grpc-port",
"50011",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:?}
- MINIO_ROOT_USER=${MINIO_ROOT_USER:?}
- POSTGRES_CONNECTION_STRING=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment?sslmode=disable
depends_on:
housekeeping: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:housekeeping"
document-conversion:
image: ghcr.io/specterops/nemesis/document-conversion:latest
volumes:
- ./infra/tika/tika-config.xml:/tika-config.xml:ro
environment:
- APP_ID=document-conversion
- DAPR_GRPC_PORT=50002
- DAPR_HTTP_PORT=3501
- TIKA_CONFIG=/tika-config.xml
# If you want to have additional language packs supported (see https://github.com/tesseract-ocr/tessdata for a full list):
# $ export TIKA_OCR_LANGUAGES="eng chi_sim chi_tra jpn rus deu spa"
# To change the default:
# - TIKA_OCR_LANGUAGES=${TIKA_OCR_LANGUAGES:-eng chi_sim chi_tra jpn rus deu spa}
# Note: each package installed will increase the image size!
- TIKA_OCR_LANGUAGES=${TIKA_OCR_LANGUAGES:-eng}
- MAX_PARALLEL_WORKFLOWS=${MAX_PARALLEL_WORKFLOWS:-5}
- MAX_WORKFLOW_EXECUTION_TIME=${MAX_WORKFLOW_EXECUTION_TIME:-300}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8000/healthz"]
interval: 10s
timeout: 5s
retries: 5
logging: *logging-config
depends_on:
postgres: { condition: service_started }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
document-conversion-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"document-conversion",
"--app-port",
"8000",
"--dapr-http-port",
"3501",
"--dapr-grpc-port",
"50002",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.yaml:ro
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
- ./infra/dapr/components/workflowstate.yaml:/dapr/components/workflowstate.yaml:ro
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
environment:
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:?}
- MINIO_ROOT_USER=${MINIO_ROOT_USER:?}
- POSTGRES_CONNECTION_STRING=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment?sslmode=disable
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
depends_on:
document-conversion: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:document-conversion"
gotenberg:
image: gotenberg/gotenberg:8.21.1
command:
[
"gotenberg",
"--api-timeout=180s",
"--libreoffice-restart-after=5",
"--libreoffice-auto-start=true",
"--prometheus-collect-interval=10s",
]
environment: { DISABLE_GOOGLE_CHROME: "1" }
healthcheck:
test: ["CMD", "curl", "-sSf", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
gotenberg-dapr:
image: "daprio/daprd:1.16.1"
command:
[
"./daprd",
"--max-body-size",
"1Gi",
"--app-id",
"gotenberg",
"--app-port",
"3000",
"--dapr-http-port",
"3505",
"--dapr-grpc-port",
"50005",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
"--enable-metrics",
"--dapr-graceful-shutdown-seconds",
"5",
]
volumes:
- ./infra/dapr/configuration/config_monitoring_${NEMESIS_MONITORING:-disabled}.yaml:/dapr/configuration/config.yaml:ro
- empty:/dapr/components:ro
depends_on:
gotenberg: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:gotenberg"
############################
# Infrastructure Services
############################
minio:
image: minio/minio:latest
environment:
MINIO_PROMETHEUS_AUTH_TYPE: public
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?}
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?}
volumes:
- minio_data:/data
command: server /data --console-address ":9001" --address ":9000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
minio-init:
image: minio/mc:latest
depends_on: [minio]
environment:
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
entrypoint:
[
"/bin/sh",
"-c",
"sleep 1; until mc alias set minio http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD}; do echo 'Minio is not online. Waiting for it to start...'; sleep 2; done && mc mb minio/loki-data --ignore-existing",
]
restart: "no"
rabbitmq:
image: rabbitmq:4.1.2-management
hostname: rabbitmq-node # have to do this for persistence reasons
environment:
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD:?}
- RABBITMQ_DEFAULT_USER=${RABBITMQ_USER:?}
- RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbitmq_management path_prefix "/rabbitmq"
- RABBITMQ_NODENAME=rabbit@rabbitmq-node # have to do this for persistence reasons
volumes:
- rabbitmq_data:/var/lib/rabbitmq
- ./infra/rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"]
interval: 10s
timeout: 10s
retries: 5
labels:
- "traefik.enable=true"
- "traefik.http.routers.rabbitmq-ui.entrypoints=websecure"
- "traefik.http.routers.rabbitmq-ui.tls=true"
- "traefik.http.services.rabbitmq-ui.loadbalancer.server.port=15672"
- "traefik.http.routers.rabbitmq-ui.rule=PathPrefix(`/rabbitmq`)"
postgres:
image: postgres:17.6-alpine
command: [
"postgres",
"-c", "max_connections=200",
"-c", "shared_buffers=256MB"
]
environment:
POSTGRES_DB: ${POSTGRES_DB:-enrichment}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
POSTGRES_USER: ${POSTGRES_USER:?}
volumes:
- ./infra/postgres:/docker-entrypoint-initdb.d:ro
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d enrichment"]
interval: 10s
timeout: 5s
retries: 5
postgres-exporter:
profiles: ["monitoring"]
image: prometheuscommunity/postgres-exporter:latest
environment:
DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER:?}:${POSTGRES_PASSWORD:?}@postgres:5432/enrichment?sslmode=disable"
volumes:
- ./infra/postgres-exporter/postgres_exporter.yml:/postgres_exporter.yml:ro
depends_on:
postgres: { condition: service_healthy }
hasura:
image: hasura/graphql-engine:v2.48.6.cli-migrations-v2
depends_on:
postgres: { condition: service_healthy }
environment:
HASURA_GRAPHQL_ADMIN_SECRET: "${HASURA_ADMIN_SECRET:-pass456}"
HASURA_GRAPHQL_BASE_PATH: "/hasura"
HASURA_GRAPHQL_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_ENABLE_METADATA_SYNC: "true"
HASURA_GRAPHQL_ENABLE_TELEMETRY: "false"
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
HASURA_GRAPHQL_LOG_LEVEL: "warn"
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/enrichment
HASURA_GRAPHQL_METADATA_DIR: /hasura-metadata
HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymous
volumes:
- ./infra/hasura/metadata:/hasura-metadata:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.hasura.rule=PathPrefix(`/hasura`)"
- "traefik.http.services.hasura.loadbalancer.server.port=8080"
- "traefik.http.routers.hasura.middlewares=graphql-stripprefix"
- "traefik.http.middlewares.graphql-stripprefix.stripprefix.prefixes=/hasura"
- "traefik.http.routers.hasura.entrypoints=websecure"
- "traefik.http.routers.hasura.tls=true"
traefik:
image: traefik:v3.4.4
command:
- "--api.insecure=true"
# - "--log.level=DEBUG"
- "--log.level=WARN"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.file=true"
- "--providers.file.directory=/config"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--metrics.prometheus=true"
- "--metrics.prometheus.addrouterslabels=true"
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.auth.basicauth.users=${BASIC_AUTH_USERS:-n:$$apr1$$RbnOjtKr$$S706SPJ3KzCKIref.gRFT.}"
- "traefik.http.routers.dashboard.tls=true"
environment:
- NEMESIS_PORT=${NEMESIS_PORT:-7443}
ports: ["${NEMESIS_PORT:-7443}:443"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./auth:/auth:ro
- ./infra/traefik/certs:/certs:ro
- ./infra/traefik/config:/config:ro
placement:
image: "daprio/dapr:1.16.1"
command: ["./placement", "-port", "50006"]
scheduler:
image: "daprio/dapr:1.16.1"
command:
[
"./scheduler",
"--port",
"50007",
"--etcd-data-dir=/var/lock/dapr/scheduler",
]
############################
# Monitoring Services (optional profile)
############################
otel-collector:
profiles: ["monitoring"]
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./infra/otel-collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
depends_on: [jaeger]
jaeger:
profiles: ["monitoring"]
image: jaegertracing/jaeger:latest # v2.x image
user: "0:0"
environment:
- QUERY_BASE_PATH=/jaeger
volumes:
- ./infra/jaeger/jaeger-config.yaml:/etc/jaeger/config.yaml
- jaeger_data:/badger # Persist trace data
command: ["--config", "/etc/jaeger/config.yaml"]
labels:
- "traefik.enable=true"
- "traefik.http.routers.jaeger.rule=PathPrefix(`/jaeger`)"
- "traefik.http.services.jaeger.loadbalancer.server.port=16686"
- "traefik.http.routers.jaeger.entrypoints=websecure"
- "traefik.http.routers.jaeger.tls=true"
- "traefik.http.routers.jaeger.middlewares=auth"
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:13133/status || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
phoenix:
profiles: ["monitoring"]
image: arizephoenix/phoenix:11.24.1
environment:
- PHOENIX_WORKING_DIR=/data
- PHOENIX_HOST_ROOT_PATH=/phoenix
- PHOENIX_SQL_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/phoenix
- PHOENIX_DEFAULT_RETENTION_POLICY_DAYS=100
volumes:
- phoenix_data:/data
# ports:
# - "6006:6006" # Phoenix UI port, for debugging
labels:
- "traefik.enable=true"
- "traefik.http.services.phoenix.loadbalancer.server.port=6006"
- "traefik.http.routers.phoenix.rule=PathPrefix(`/phoenix`)"
- "traefik.http.routers.phoenix.entrypoints=websecure"
- "traefik.http.routers.phoenix.tls=true"
- "traefik.http.routers.phoenix.middlewares=auth,phoenix-stripprefix"
- "traefik.http.middlewares.phoenix-stripprefix.stripprefix.prefixes=/phoenix"
depends_on:
postgres: { condition: service_healthy }
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:6006/healthz').read()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
loki:
profiles: ["monitoring"]
image: grafana/loki:3.3.2
command:
["-config.file=/etc/loki/local-config.yaml", "-config.expand-env=true"]
environment:
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
volumes:
- ./infra/loki:/etc/loki:ro
- loki_data:/loki
depends_on: [minio-init]
healthcheck:
test:
[
"CMD-SHELL",
"wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1",
]
interval: 10s
promtail:
profiles: ["monitoring"]
image: grafana/promtail:3.3.2
user: "0:0"
volumes:
- ./infra/promtail:/etc/promtail:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command:
["-config.file=/etc/promtail/config.yaml", "-config.expand-env=true"]
depends_on: [loki]
grafana:
profiles: ["monitoring"]
image: grafana/grafana:11.4.0
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Editor
# - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-Qwerty12345}
# - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-nemesis}
- GF_SERVER_DOMAIN=${EXTERNAL_HOST:-https://localhost}
- GF_SERVER_ROOT_URL=${EXTERNAL_HOST:-https://localhost}/grafana
- GF_SERVER_SERVE_FROM_SUB_PATH=true
volumes:
- ./infra/grafana/provisioning:/etc/grafana/provisioning:ro
- grafana_data:/var/lib/grafana
depends_on:
postgres: { condition: service_healthy }
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=PathPrefix(`/grafana`)"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true"
- "traefik.http.routers.grafana.middlewares=auth"
healthcheck:
test: ["CMD", "curl", "-sSf", "http://localhost:3000/"]
interval: 10s
prometheus:
profiles: ["monitoring"]
image: prom/prometheus:v3.1.0
volumes:
- ./infra/prometheus:/etc/prometheus:ro
- prometheus_data:/prometheus
command:
[
"--config.file=/etc/prometheus/prometheus.yml",
"--storage.tsdb.path=/prometheus",
"--web.external-url=/prometheus/",
]
healthcheck:
test:
[
"CMD",
"wget",
"--spider",
"-q",
"http://localhost:9090/prometheus/-/healthy",
]
interval: 10s
labels:
- "traefik.enable=true"
- "traefik.http.routers.prometheus.rule=PathPrefix(`/prometheus`)"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
- "traefik.http.routers.prometheus.entrypoints=websecure"
- "traefik.http.routers.prometheus.tls=true"
- "traefik.http.routers.prometheus.middlewares=auth"
node-exporter:
profiles: ["monitoring"]
image: prom/node-exporter:latest
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
[
"--path.procfs=/host/proc",
"--path.rootfs=/rootfs",
"--path.sysfs=/host/sys",
"--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)",
]
cadvisor:
profiles: ["monitoring"]
image: gcr.io/cadvisor/cadvisor:latest
restart: unless-stopped
privileged: true
volumes:
- /:/rootfs:ro
- /dev/disk/:/dev/disk:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /var/run:/var/run:ro
devices: ["/dev/kmsg:/dev/kmsg"]
############################
# LLM Services (optional profile)
############################
litellm:
profiles: ["llm"]
image: ghcr.io/berriai/litellm:v1.74.0-stable
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/litellm
- STORE_MODEL_IN_DB=True
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
- AWS_REGION_NAME=${AWS_REGION_NAME:-us-east-1}
- LITELLM_MASTER_KEY=sk-${LITELLM_ADMIN_PASSWORD:-admin123}
- LITELLM_SALT_KEY=sk-${LITELLM_ADMIN_PASSWORD:-admin123}
- SERVER_ROOT_PATH=/llm
- PROXY_BASE_URL=${NEMESIS_URL:?}
- UI_BASE_PATH=/llm/ui
volumes:
- ./infra/litellm/config.yml:/app/config.yml:ro
command: ["--config", "/app/config.yml"]
depends_on:
postgres: { condition: service_healthy }
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "-O", "/dev/null", "--header=Authorization: Bearer sk-${LITELLM_ADMIN_PASSWORD:-admin123}", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
disable: true
logging: *logging-config
labels:
- "traefik.enable=true"
- "traefik.http.routers.litellm.rule=PathPrefix(`/llm`)"
- "traefik.http.services.litellm.loadbalancer.server.port=4000"
- "traefik.http.routers.litellm.entrypoints=websecure"
- "traefik.http.routers.litellm.tls=true"