Files
Lee Chagolla-Christensen 4ae0694576 chore: bump dapr versions
Update Docker Compose and Kubernetes deployment paths to use Dapr 1.17.7 consistently, including Helm setup scripts and sidecar image annotations.
2026-05-21 18:07:37 -07:00

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