mirror of
https://github.com/SpecterOps/Nemesis
synced 2026-06-08 12:36:42 +00:00
860 lines
28 KiB
YAML
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"]
|