mirror of
https://github.com/SpecterOps/Nemesis
synced 2026-06-08 12:36:42 +00:00
4ae0694576
Update Docker Compose and Kubernetes deployment paths to use Dapr 1.17.7 consistently, including Helm setup scripts and sidecar image annotations.
1237 lines
44 KiB
YAML
1237 lines
44 KiB
YAML
name: nemesis
|
|
|
|
networks:
|
|
nemesis:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
grafana_data:
|
|
jaeger_data:
|
|
loki_data:
|
|
seaweedfs_data:
|
|
phoenix_data:
|
|
postgres_data:
|
|
prometheus_data:
|
|
rabbitmq_data:
|
|
scheduler_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}
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
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.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--config",
|
|
"/dapr/configuration/config.yaml",
|
|
"--enable-metrics",
|
|
"--dapr-graceful-shutdown-seconds",
|
|
"5",
|
|
]
|
|
volumes:
|
|
- ./infra/dapr/components/pubsub/files.yaml:/dapr/components/files.yaml:ro
|
|
- ./infra/dapr/components/pubsub/workflow_monitor.yaml:/dapr/components/workflow_monitor.yaml:ro
|
|
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
|
|
- ./infra/dapr/configuration/general.yaml:/dapr/configuration/config.yaml:ro
|
|
environment:
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
|
- POSTGRES_USER=${POSTGRES_USER:?}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?}
|
|
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
|
|
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
|
- POSTGRES_DB=${POSTGRES_DB:-enrichment}
|
|
- POSTGRES_PARAMETERS=${POSTGRES_PARAMETERS:-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"
|
|
|
|
titus-scanner:
|
|
image: ghcr.io/specterops/nemesis/titus-scanner:latest
|
|
depends_on:
|
|
seaweedfs: { condition: service_healthy }
|
|
rabbitmq: { condition: service_healthy }
|
|
environment:
|
|
- DAPR_HTTP_PORT=3513
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_BUCKET=files
|
|
- S3_ENDPOINT=http://seaweedfs:8333
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
- LOG_LEVEL=info
|
|
- SNIPPET_LENGTH=${SNIPPET_LENGTH:-512} # context length around any Titus matches
|
|
- MAX_CONCURRENT_FILES=${TITUS_MAX_CONCURRENT_FILES:-2} # maximum number of concurrent files to scan
|
|
- MAX_MATCHES_PER_FILE=${MAX_MATCHES_PER_FILE:-100} # safety cap on matches per file
|
|
- MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-200} # maximum file size to scan, in megabytes
|
|
- GOMEMLIMIT=1500MiB # soft memory limit for Go GC to be aggressive before kernel OOM kills us
|
|
- EXTRACT_ARCHIVES=${EXTRACT_ARCHIVES:-true} # extract+scan archive contents (zip, jar, war, ear, apk, tar, tar.gz, 7z)
|
|
- EXTRACT_MAX_FILE_SIZE_MB=${EXTRACT_MAX_FILE_SIZE_MB:-10} # max per-file size within archives (MB)
|
|
- EXTRACT_MAX_TOTAL_SIZE_MB=${EXTRACT_MAX_TOTAL_SIZE_MB:-1000} # total extraction budget per archive (MB)
|
|
- EXTRACT_MAX_DEPTH=${EXTRACT_MAX_DEPTH:-2} # max nesting depth for recursive archives
|
|
- ENABLE_VALIDATION=${ENABLE_VALIDATION:-false} # whether to enable credential validation
|
|
- VALIDATION_WORKERS=${VALIDATION_WORKERS:-4} # concurrent validation workers
|
|
- DISABLED_RULES=${DISABLED_RULES:-np.s3.1,np.s3.2,np.linkedin.3,kingfisher.jira.1} # comma-separated rule IDs to disable (np.s3.x have backtracking-prone patterns that timeout on large files, np.linkedin.3 / kingfisher.jira.1 have a lot of false positives)
|
|
- PUBSUB_NAME=titus # Name of the Dapr pubsub resource
|
|
- OUTPUT_TOPIC=titus_output # Name of the output topic in Dapr
|
|
- BULK_MAX_MESSAGES=${BULK_MAX_MESSAGES:-100} # max messages per bulk subscribe batch
|
|
- BULK_MAX_AWAIT_DURATION_MS=${BULK_MAX_AWAIT_DURATION_MS:-1000} # max wait before delivering partial batch (ms)
|
|
volumes:
|
|
- ./projects/titus_scanner/custom_rules/:/opt/titus:ro
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "1"
|
|
memory: 2G
|
|
titus-scanner-dapr:
|
|
image: "daprio/daprd:1.17.7"
|
|
command:
|
|
[
|
|
"./daprd",
|
|
"--max-body-size",
|
|
"1Gi",
|
|
"--app-id",
|
|
"titus-scanner",
|
|
"--app-port",
|
|
"8080",
|
|
"--app-protocol",
|
|
"http",
|
|
"--dapr-http-port",
|
|
"3513",
|
|
"--placement-host-address",
|
|
"placement:50006",
|
|
"--resources-path",
|
|
"/dapr/components",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--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/titus.yaml:/dapr/components/titus.yaml:ro
|
|
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
|
|
- ./infra/dapr/configuration/general.yaml:/dapr/configuration/config.yaml:ro
|
|
depends_on: [titus-scanner, placement]
|
|
network_mode: "service:titus-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:
|
|
seaweedfs: { condition: service_healthy }
|
|
rabbitmq: { condition: service_healthy }
|
|
environment:
|
|
- DAPR_GRPC_PORT=50014
|
|
- DAPR_PORT=3514
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_BUCKET=files
|
|
- S3_ENDPOINT=http://seaweedfs:8333
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
- ASPNETCORE_URLS=http://0.0.0.0:5000
|
|
expose:
|
|
- "5000"
|
|
dotnet-service-dapr:
|
|
image: "daprio/daprd:1.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--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/dotnet.yaml:/dapr/components/dotnet.yaml:ro
|
|
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
|
|
- ./infra/dapr/configuration/general.yaml:/dapr/configuration/config.yaml:ro
|
|
depends_on: [dotnet-service, placement]
|
|
network_mode: "service:dotnet-service"
|
|
|
|
file-enrichment: &file-enrichment-template
|
|
image: ghcr.io/specterops/nemesis/file-enrichment:latest
|
|
healthcheck: &file-enrichment-healthcheck
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8001/healthz"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
volumes: []
|
|
# Uncomment the following line to use custom YARA rules (*.yar/*.yara files)
|
|
# - ./libs/file_enrichment_modules/yara_rules/prod/:/yara_rules/:ro
|
|
environment: &file-enrichment-environment
|
|
ENABLE_PII_DETECTION: ${ENABLE_PII_DETECTION:-false}
|
|
PII_DETECTION_THRESHOLD: ${PII_DETECTION_THRESHOLD:-0.7}
|
|
APP_ID: file-enrichment
|
|
DAPR_GRPC_PORT: 50003
|
|
DAPR_HTTP_PORT: 3503
|
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
|
MAX_PARALLEL_WORKFLOWS: ${ENRICHMENT_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
|
|
# PYTHONASYNCIOTHREADPOOLSIZE: 100 # Increase thread pool for asyncio.to_thread()
|
|
RIGGING_GENERATOR_CREDENTIALS: ${RIGGING_GENERATOR_CREDENTIALS:-}
|
|
RIGGING_GENERATOR_SUMMARY: ${RIGGING_GENERATOR_SUMMARY:-}
|
|
RIGGING_GENERATOR_TRIAGE: ${RIGGING_GENERATOR_TRIAGE:-}
|
|
WORKFLOW_RUNTIME_LOG_LEVEL: ${WORKFLOW_RUNTIME_LOG_LEVEL:-WARNING}
|
|
WORKFLOW_CLIENT_LOG_LEVEL: ${WORKFLOW_CLIENT_LOG_LEVEL:-WARNING}
|
|
logging: *logging-config
|
|
depends_on: &file-enrichment-depends-on
|
|
postgres: { condition: service_started }
|
|
placement: { condition: service_started }
|
|
scheduler: { condition: service_started }
|
|
rabbitmq: { condition: service_healthy }
|
|
file-enrichment-dapr: &file-enrichment-dapr-template
|
|
image: "daprio/daprd:1.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-warn}",
|
|
"--resources-path",
|
|
"/dapr/components",
|
|
"--config",
|
|
"/dapr/configuration/file_enrichment_monitoring_${NEMESIS_MONITORING:-disabled}.yaml",
|
|
"--enable-metrics",
|
|
"--dapr-graceful-shutdown-seconds",
|
|
"5",
|
|
]
|
|
volumes: &file-enrichment-dapr-volumes
|
|
- ./infra/dapr/components/pubsub/alerting.yaml:/dapr/components/alerting.yaml:ro
|
|
- ./infra/dapr/components/pubsub/document_conversion.yaml:/dapr/components/document_conversion.yaml:ro
|
|
- ./infra/dapr/components/pubsub/dotnet.yaml:/dapr/components/dotnet.yaml:ro
|
|
- ./infra/dapr/components/pubsub/dpapi.yaml:/dapr/components/dpapi.yaml:ro
|
|
- ./infra/dapr/components/pubsub/files.yaml:/dapr/components/files.yaml:ro
|
|
- ./infra/dapr/components/pubsub/titus.yaml:/dapr/components/titus.yaml:ro
|
|
- ./infra/dapr/components/pubsub/workflow_monitor.yaml:/dapr/components/workflow_monitor.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/:/dapr/configuration/
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:?}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
|
|
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
|
|
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
|
|
POSTGRES_DB: ${POSTGRES_DB:-enrichment}
|
|
POSTGRES_PARAMETERS: ${POSTGRES_PARAMETERS:-sslmode=disable}
|
|
RABBITMQ_CONNECTION_STRING: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?}
|
|
S3_SECRET_KEY: ${S3_SECRET_KEY:?}
|
|
MAX_PARALLEL_WORKFLOWS: ${MAX_PARALLEL_WORKFLOWS:-5}
|
|
depends_on: &file-enrichment-dapr-depends-on
|
|
file-enrichment: { condition: service_started }
|
|
placement: { condition: service_started }
|
|
scheduler: { condition: service_started }
|
|
rabbitmq: { condition: service_healthy }
|
|
network_mode: "service:file-enrichment"
|
|
|
|
# file-enrichment-1:
|
|
# <<: *file-enrichment-template
|
|
# environment:
|
|
# <<: *file-enrichment-environment
|
|
# APP_ID: file-enrichment-1
|
|
# file-enrichment-1-dapr:
|
|
# <<: *file-enrichment-dapr-template
|
|
# depends_on:
|
|
# file-enrichment-1: { condition: service_started }
|
|
# placement: { condition: service_started }
|
|
# scheduler: { condition: service_started }
|
|
# rabbitmq: { condition: service_healthy }
|
|
# network_mode: "service:file-enrichment-1"
|
|
|
|
# file-enrichment-2:
|
|
# <<: *file-enrichment-template
|
|
# environment:
|
|
# <<: *file-enrichment-environment
|
|
# APP_ID: file-enrichment-2
|
|
# file-enrichment-2-dapr:
|
|
# <<: *file-enrichment-dapr-template
|
|
# depends_on:
|
|
# file-enrichment-2: { condition: service_started }
|
|
# placement: { condition: service_started }
|
|
# scheduler: { condition: service_started }
|
|
# rabbitmq: { condition: service_healthy }
|
|
# network_mode: "service:file-enrichment-2"
|
|
|
|
# file-enrichment-3:
|
|
# <<: *file-enrichment-template
|
|
# environment:
|
|
# <<: *file-enrichment-environment
|
|
# APP_ID: file-enrichment-3
|
|
# file-enrichment-3-dapr:
|
|
# <<: *file-enrichment-dapr-template
|
|
# depends_on:
|
|
# file-enrichment-3: { condition: service_started }
|
|
# placement: { condition: service_started }
|
|
# scheduler: { condition: service_started }
|
|
# rabbitmq: { condition: service_healthy }
|
|
# network_mode: "service:file-enrichment-3"
|
|
|
|
frontend:
|
|
image: ghcr.io/specterops/nemesis/frontend:latest
|
|
depends_on: [postgres, hasura]
|
|
environment:
|
|
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
|
|
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:?}
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
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: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
alerting-dapr:
|
|
image: "daprio/daprd:1.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--config",
|
|
"/dapr/configuration/config.yaml",
|
|
"--enable-metrics",
|
|
"--dapr-graceful-shutdown-seconds",
|
|
"5",
|
|
]
|
|
volumes:
|
|
- ./infra/dapr/components/pubsub/alerting.yaml:/dapr/components/alerting.yaml:ro
|
|
- ./infra/dapr/components/secretstore.yaml:/dapr/components/secretstore.yaml:ro
|
|
- ./infra/dapr/configuration/general.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"
|
|
|
|
housekeeping:
|
|
image: ghcr.io/specterops/nemesis/housekeeping:latest
|
|
environment:
|
|
- APP_ID=housekeeping
|
|
- CLEANUP_SCHEDULE=*/5 * * * * # Every 5 minutes
|
|
- DAPR_GRPC_PORT=50011
|
|
- DAPR_HTTP_PORT=3510
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
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.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--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/general.yaml:/dapr/configuration/config.yaml:ro
|
|
environment:
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
- POSTGRES_USER=${POSTGRES_USER:?}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?}
|
|
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
|
|
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
|
- POSTGRES_DB=${POSTGRES_DB:-enrichment}
|
|
- POSTGRES_PARAMETERS=${POSTGRES_PARAMETERS:-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${TIKA_USE_OCR:+-tesseract}.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=${DOCUMENTCONVERSION_WORKERS:-5}
|
|
- MAX_WORKFLOW_EXECUTION_TIME=${MAX_WORKFLOW_EXECUTION_TIME:-300}
|
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
|
- OMP_THREAD_LIMIT=1 # Limit the number of tesseract instances
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8000/healthz"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
logging: *logging-config
|
|
depends_on:
|
|
postgres: { condition: service_started }
|
|
placement: { condition: service_started }
|
|
rabbitmq: { condition: service_healthy }
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "2"
|
|
document-conversion-dapr:
|
|
image: "daprio/daprd:1.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--config",
|
|
"/dapr/configuration/document_conversion_monitoring_${NEMESIS_MONITORING:-disabled}.yaml",
|
|
"--enable-metrics",
|
|
"--dapr-graceful-shutdown-seconds",
|
|
"5",
|
|
]
|
|
volumes:
|
|
- ./infra/dapr/components/pubsub/document_conversion.yaml:/dapr/components/document_conversion.yaml:ro
|
|
- ./infra/dapr/components/pubsub/files.yaml:/dapr/components/files.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/:/dapr/configuration/
|
|
environment:
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
- POSTGRES_USER=${POSTGRES_USER:?}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?}
|
|
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
|
|
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
|
- POSTGRES_DB=${POSTGRES_DB:-enrichment}
|
|
- POSTGRES_PARAMETERS=${POSTGRES_PARAMETERS:-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
|
|
image: gotenberg/gotenberg:8.27.0
|
|
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.17.7"
|
|
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",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
"--config",
|
|
"/dapr/configuration/config.yaml",
|
|
"--enable-metrics",
|
|
"--dapr-graceful-shutdown-seconds",
|
|
"5",
|
|
]
|
|
volumes:
|
|
- ./infra/dapr/configuration/general.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
|
|
############################
|
|
seaweedfs:
|
|
image: chrislusf/seaweedfs:4.15
|
|
environment:
|
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY:?}
|
|
S3_SECRET_KEY: ${S3_SECRET_KEY:?}
|
|
volumes:
|
|
- seaweedfs_data:/data
|
|
- ./infra/seaweedfs/entrypoint.sh:/entrypoint.sh:ro
|
|
entrypoint: ["/entrypoint.sh"]
|
|
command: server -s3 -s3.port=8333 -s3.config=/etc/seaweedfs/s3.json -master.port=9333 -volume.port=8080 -volume.max=0 -metricsPort=9324 -dir=/data
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8333/status"]
|
|
interval: 30s
|
|
timeout: 20s
|
|
retries: 3
|
|
|
|
seaweedfs-init:
|
|
image: amazon/aws-cli:2.27.28
|
|
depends_on:
|
|
seaweedfs: { condition: service_healthy }
|
|
environment:
|
|
- AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY}
|
|
- AWS_SECRET_ACCESS_KEY=${S3_SECRET_KEY}
|
|
- AWS_DEFAULT_REGION=us-east-1
|
|
entrypoint:
|
|
[
|
|
"/bin/sh",
|
|
"-c",
|
|
"aws --endpoint-url http://seaweedfs:8333 s3 mb s3://loki-data --region us-east-1 2>/dev/null; aws --endpoint-url http://seaweedfs:8333 s3 mb s3://files --region us-east-1 2>/dev/null; echo 'SeaweedFS bucket initialization complete.'; true",
|
|
]
|
|
restart: "no"
|
|
|
|
rabbitmq:
|
|
# image: rabbitmq:4.2.0-management
|
|
image: rabbitmq:4.2.4-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
|
|
- ./infra/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf: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:18.1
|
|
command:
|
|
[
|
|
"postgres",
|
|
"-c",
|
|
"max_connections=200",
|
|
"-c",
|
|
"shared_buffers=1GB",
|
|
"-c",
|
|
"shared_preload_libraries=pg_stat_statements",
|
|
"-c",
|
|
"pg_stat_statements.track=all",
|
|
"-c",
|
|
"tcp_keepalives_idle=5",
|
|
"-c",
|
|
"tcp_keepalives_interval=2",
|
|
"-c",
|
|
"tcp_keepalives_count=3",
|
|
]
|
|
# ports:
|
|
# - "${POSTGRES_EXTERNAL_PORT:-}5432"
|
|
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
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d enrichment"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
postgres-exporter:
|
|
profiles: ["monitoring"]
|
|
image: prometheuscommunity/postgres-exporter:v0.18.1
|
|
environment:
|
|
DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER:?}:${POSTGRES_PASSWORD:?}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-enrichment}?${POSTGRES_PARAMETERS:-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
|
|
image: hasura/graphql-engine:v2.48.12.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: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-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: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-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.6.1
|
|
image: traefik:v3.6.9
|
|
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.17.7"
|
|
command:
|
|
[
|
|
"./placement",
|
|
"-port",
|
|
"50006",
|
|
"--enable-metrics",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
]
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
|
|
scheduler-init:
|
|
image: busybox:1.37.0
|
|
user: "0:0"
|
|
command: >
|
|
sh -c "
|
|
mkdir -p /data/default-dapr-scheduler-server-0 &&
|
|
chown -R 65532:65532 /data &&
|
|
chmod -R 755 /data
|
|
"
|
|
volumes:
|
|
- scheduler_data:/data
|
|
restart: "no"
|
|
|
|
scheduler:
|
|
image: "daprio/scheduler:1.17.7"
|
|
command:
|
|
[
|
|
"./scheduler",
|
|
"--port",
|
|
"50007",
|
|
"--etcd-data-dir=/data",
|
|
"--enable-metrics",
|
|
"--log-level",
|
|
"${DAPR_LOG_LEVEL:-info}",
|
|
]
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
scheduler-init: { condition: service_completed_successfully }
|
|
volumes:
|
|
- scheduler_data:/data
|
|
|
|
############################
|
|
# Monitoring Services (optional profile)
|
|
############################
|
|
|
|
otel-collector:
|
|
profiles: ["monitoring"]
|
|
image: otel/opentelemetry-collector:0.139.0
|
|
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:2.11.0 # v2.x image
|
|
image: jaegertracing/jaeger:2.15.1
|
|
user: "0:0"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 2G
|
|
reservations:
|
|
memory: 1G
|
|
environment:
|
|
- QUERY_BASE_PATH=/jaeger
|
|
- GOGC=80
|
|
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
|
|
|
|
loki:
|
|
profiles: ["monitoring"]
|
|
# image: grafana/loki:3.3.2
|
|
image: grafana/loki:3.5.11
|
|
command:
|
|
["-config.file=/etc/loki/local-config.yaml", "-config.expand-env=true"]
|
|
environment:
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY}
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY}
|
|
volumes:
|
|
- ./infra/loki:/etc/loki:ro
|
|
- loki_data:/loki
|
|
depends_on: [seaweedfs-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
|
|
image: grafana/promtail:3.5.11
|
|
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
|
|
image: grafana/grafana:12.3.4
|
|
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
|
|
- POSTGRES_CONNECTION_STRING=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-enrichment}?${POSTGRES_PARAMETERS:-sslmode=disable}
|
|
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
|
|
image: prom/prometheus:v3.10.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:v1.10.2
|
|
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: ghcr.io/google/cadvisor:v0.53.0
|
|
restart: unless-stopped
|
|
privileged: true
|
|
command:
|
|
- "--housekeeping_interval=30s"
|
|
- "--storage_duration=2m0s"
|
|
- "--docker_only=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)
|
|
############################
|
|
|
|
agents:
|
|
profiles: ["llm"]
|
|
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_HOST:-postgres}:${POSTGRES_PORT:-5432}/phoenix
|
|
- NEMESIS_MONITORING=${NEMESIS_MONITORING:-disabled}
|
|
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel-collector:4317
|
|
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_INSECURE=true
|
|
# Chatbot configuration
|
|
- CHATBOT_DB_PASSWORD=${CHATBOT_DB_PASSWORD:-chatbot_pass_change_me}
|
|
- MCP_MAX_RESULTS=${MCP_MAX_RESULTS:-100}
|
|
logging: *logging-config
|
|
depends_on:
|
|
postgres: { condition: service_healthy }
|
|
placement: { condition: service_started }
|
|
rabbitmq: { condition: service_healthy }
|
|
hasura: { condition: service_healthy }
|
|
litellm: { condition: service_healthy }
|
|
healthcheck: *healthcheck-python-svc
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.mcp.rule=PathPrefix(`/mcp`)"
|
|
- "traefik.http.services.mcp.loadbalancer.server.port=5000"
|
|
- "traefik.http.routers.mcp.entrypoints=websecure"
|
|
- "traefik.http.routers.mcp.tls=true"
|
|
- "traefik.http.routers.mcp.middlewares=auth"
|
|
agents-dapr:
|
|
profiles: ["llm"]
|
|
image: "daprio/daprd:1.17.7"
|
|
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/agents_monitoring_${NEMESIS_MONITORING:-disabled}.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/:/dapr/configuration/
|
|
environment:
|
|
- POSTGRES_USER=${POSTGRES_USER:?}
|
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?}
|
|
- POSTGRES_HOST=${POSTGRES_HOST:-postgres}
|
|
- POSTGRES_PORT=${POSTGRES_PORT:-5432}
|
|
- POSTGRES_DB=${POSTGRES_DB:-enrichment}
|
|
- POSTGRES_PARAMETERS=${POSTGRES_PARAMETERS:-sslmode=disable}
|
|
- HASURA_ADMIN_SECRET=${HASURA_ADMIN_SECRET:-pass456}
|
|
- RABBITMQ_CONNECTION_STRING=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
|
- S3_ACCESS_KEY=${S3_ACCESS_KEY:?}
|
|
- S3_SECRET_KEY=${S3_SECRET_KEY:?}
|
|
depends_on:
|
|
agents: { condition: service_started }
|
|
placement: { condition: service_started }
|
|
scheduler: { condition: service_started }
|
|
rabbitmq: { condition: service_healthy }
|
|
network_mode: "service:agents"
|
|
|
|
litellm:
|
|
profiles: ["llm"]
|
|
# image: ghcr.io/berriai/litellm:v1.74.0-stable
|
|
image: ghcr.io/berriai/litellm:v1.83.14-stable
|
|
environment:
|
|
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-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-SHELL",
|
|
"python3 -c \"import urllib.request,json; r=urllib.request.urlopen(urllib.request.Request('http://localhost:4000/health',headers={'Authorization':'Bearer sk-${LITELLM_ADMIN_PASSWORD:-admin123}'})); d=json.load(r); exit(0 if d.get('healthy_count',0)>0 and d.get('unhealthy_count',0)==0 else 1)\"",
|
|
]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
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"
|
|
|
|
phoenix:
|
|
profiles: ["llm"]
|
|
# image: arizephoenix/phoenix:11.24.1
|
|
image: arizephoenix/phoenix:13.3.0
|
|
environment:
|
|
- PHOENIX_WORKING_DIR=/data
|
|
- PHOENIX_HOST_ROOT_PATH=/phoenix
|
|
- PHOENIX_SQL_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-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
|