Files
SpecterOps-Nemesis/compose.yaml
T
2025-06-25 02:21:21 -07:00

860 lines
28 KiB
YAML

name: nemesis
networks:
nemesis:
driver: bridge
volumes:
grafana_data:
jaeger_data:
loki_data:
minio_data:
postgres_data:
prometheus_data:
rabbitmq_data:
empty:
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
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.15.5"
command:
[
"./daprd",
"--app-id",
"web-api",
"--max-body-size",
"300Mi",
"--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",
]
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
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
volumes:
- ./projects/noseyparker_scanner/custom_rules/:/opt/noseyparker:ro
noseyparker-scanner-dapr:
image: "daprio/daprd:1.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
environment:
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
volumes:
- ./infra/dapr/components/pubsub.yaml:/dapr/components/pubsub.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-api:
image: ghcr.io/specterops/nemesis/dotnet-api:latest
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:1337/healthz"]
interval: 10s
timeout: 5s
retries: 5
environment:
- APP_ID=dotnet-api
- DAPR_GRPC_PORT=50010
- DAPR_HTTP_PORT=3507
logging: *logging-config
depends_on:
postgres: { condition: service_started }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
dotnet-api-dapr:
image: "daprio/daprd:1.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--app-id",
"dotnet-api",
"--app-port",
"1337",
"--dapr-http-port",
"3507",
"--dapr-grpc-port",
"50010",
"--placement-host-address",
"placement:50006",
"--scheduler-host-address",
"scheduler:50007",
"--resources-path",
"/dapr/components",
"--config",
"/dapr/configuration/config.yaml",
]
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=host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=enrichment port=5432 sslmode=disable
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
depends_on:
dotnet-api: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:dotnet-api"
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:
- ./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=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_URL=${NEMESIS_URL:?}
- RIGGING_GENERATOR_CREDENTIALS=${RIGGING_GENERATOR_CREDENTIALS:-}
- RIGGING_GENERATOR_SUMMARY=${RIGGING_GENERATOR_SUMMARY:-}
- RIGGING_GENERATOR_TRIAGE=${RIGGING_GENERATOR_TRIAGE:-}
- UVICORN_WORKERS=${UVICORN_WORKERS:-2}
logging: *logging-config
depends_on:
postgres: { condition: service_started }
placement: { condition: service_started }
rabbitmq: { condition: service_healthy }
file-enrichment-dapr:
image: "daprio/daprd:1.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
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=host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=enrichment port=5432 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: ["analysis"]
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/"]
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.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
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"
triage:
image: ghcr.io/specterops/nemesis/triage:latest
environment:
- DAPR_HTTP_PORT=3509
- DAPR_GRPC_PORT=50009
- NEMESIS_URL=${NEMESIS_URL:?}
- RIGGING_GENERATOR_TRIAGE=${RIGGING_GENERATOR_TRIAGE:-}
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
triage-dapr:
image: "daprio/daprd:1.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--app-id",
"triage",
"--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",
]
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:
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
depends_on:
triage: { condition: service_started }
placement: { condition: service_started }
scheduler: { condition: service_started }
rabbitmq: { condition: service_healthy }
network_mode: "service:triage"
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.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
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=host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=enrichment port=5432 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
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.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
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=host=postgres user=${POSTGRES_USER} password=${POSTGRES_PASSWORD} dbname=enrichment port=5432 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",
]
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.15.5"
command:
[
"./daprd",
"--max-body-size",
"300Mi",
"--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",
]
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.1-management
environment:
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD:?}
- RABBITMQ_DEFAULT_USER=${RABBITMQ_USER:?}
- RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS=-rabbitmq_management path_prefix "/rabbitmq"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
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.5-alpine
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
hasura:
image: hasura/graphql-engine:v2.45.1.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.3.2
command:
- "--api.insecure=true"
- "--log.level=WARN"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--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
placement:
image: "daprio/dapr:1.15.5"
command: ["./placement", "-port", "50006"]
scheduler:
image: "daprio/dapr:1.15.5"
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:
image: jaegertracing/jaeger:latest # v2.x image
user: "0:0"
environment:
- QUERY_BASE_PATH=/jaeger
volumes:
- jaeger_data:/badger # Persist trace data
- ./infra/jaeger/jaeger-config.yaml:/etc/jaeger/config.yaml
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
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=Viewer
# - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-}
# - 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"]