# Tamga — production Docker Compose stack. # # SECURITY NOTES: # - In the full profile only the TLS edge (Caddy) is published: HTTPS on # 8443 for the proxy and 3000 for the console. All other services live on # internal bridge networks. Plain HTTP only when TAMGA_PROXY_SITE and # TAMGA_CONSOLE_SITE say http:// (docs/operations.md, "TLS"). # - The proxy authenticates to the analyzer, classifier and scanner service # with a token per service; with TAMGA_REQUIRE_SERVICE_TOKENS on (the # default here) nothing starts without them. # - Postgres and Redis credentials are read from env vars — never hardcoded. # - Redis requires AUTH (requirepass). # - OTLP sample rate is set to 5% for production. # - Every service has CPU and memory limits; each can be changed in .env # (docs/operations.md, "Resource limits"). # # IMAGES: third-party images are pinned by tag. TODO: add the digest to each # (name:tag@sha256:...); no digest was recorded in the repository when the # tags were written. `docker buildx imagetools inspect ` prints it. # Dependabot (.github/dependabot.yml) keeps tags and digests current. # # PROFILES: every service belongs to a profile, so a command must name one. # - full: TLS edge, proxy, analyzer, scanner service, console, PostgreSQL, # Redis, NATS and Jaeger. .env.example sets COMPOSE_PROFILES=full, so # with `--env-file .env` and no --profile flag this is what starts. # - minimal: the proxy alone (service proxy-minimal), with no database. A # --profile flag replaces COMPOSE_PROFILES, so `--profile minimal` starts # only this one container. What it does without the other services is in # docs/operations.md, "Proxy only". It serves plain HTTP unless the proxy # is given a certificate (TAMGA_TLS_CERT, TAMGA_TLS_KEY). # - classifier: the optional inline classifier, added to full. # Do not start minimal and full together: both publish port 8443. services: # The TLS edge: Caddy in front of the proxy and the console. With the # default site addresses it serves https://localhost:8443 and # https://localhost:3000 with certificates from its own local CA, kept in # the edgedata volume. A public name in TAMGA_PROXY_SITE and # TAMGA_CONSOLE_SITE gets a certificate from Let's Encrypt (port 80 must # reach the edge: docker-compose.public.yml). http:// addresses serve plain # HTTP. The site addresses keep the container ports 8443 and 3000; # TAMGA_PROXY_PORT and TAMGA_DASHBOARD_PORT choose the host ports. edge: profiles: ["full"] image: caddy:2.11.7-alpine # TODO: pin by digest (see IMAGES above) ports: - "${TAMGA_PROXY_BIND:-0.0.0.0}:${TAMGA_PROXY_PORT:-8443}:8443" - "${TAMGA_DASHBOARD_BIND:-0.0.0.0}:${TAMGA_DASHBOARD_PORT:-3000}:3000" environment: - TAMGA_PROXY_SITE=${TAMGA_PROXY_SITE:-https://localhost:8443} - TAMGA_CONSOLE_SITE=${TAMGA_CONSOLE_SITE:-https://localhost:3000} - TAMGA_PROXY_UPSTREAM=proxy:${TAMGA_PROXY_PORT:-8443} volumes: - ./edge/Caddyfile:/etc/caddy/Caddyfile:ro - edgedata:/data - edgeconfig:/config depends_on: proxy: condition: service_healthy dashboard: condition: service_started networks: edge: # Fixed, so the proxy can trust it for X-Forwarded-For. ipv4_address: ${TAMGA_EDGE_ADDR:-10.213.98.2} restart: unless-stopped deploy: resources: limits: cpus: "${TAMGA_EDGE_CPUS:-1}" memory: ${TAMGA_EDGE_MEMORY:-256m} reservations: memory: 32m # Standalone scanner service — optional. By default the proxy scans in-process # and this service sits idle. Set TAMGA_SCANNER_SERVICE_ADDR=scanner-service:50052 # in .env to delegate the stateless scanners (PII, secrets, injection, # jailbreak, moderation) to it over gRPC. Scanners that need the live policy or # the request (custom entities, competitors, operator_state) always run in the # proxy, and a gRPC failure falls back to in-process scanning. scanner-service: profiles: ["full"] build: context: ../proxy dockerfile: cmd/scanner-service/Dockerfile expose: - "${SCANNER_SERVICE_PORT:-50052}" environment: - SCANNER_SERVICE_PORT=${SCANNER_SERVICE_PORT:-50052} - SCANNER_PIPELINE_MODE=${SCANNER_PIPELINE_MODE:-adaptive} - SCANNER_WORKER_POOL_SIZE=${SCANNER_WORKER_POOL_SIZE:-4} - SCANNER_WORKER_QUEUE_SIZE=${SCANNER_WORKER_QUEUE_SIZE:-0} # The proxy's token for this service. Required: the service does not # start without it while TAMGA_REQUIRE_SERVICE_TOKENS is on. - TAMGA_SCANNER_SERVICE_TOKEN=${TAMGA_SCANNER_SERVICE_TOKEN:-} - TAMGA_REQUIRE_SERVICE_TOKENS=${TAMGA_REQUIRE_SERVICE_TOKENS:-true} networks: - internal restart: unless-stopped healthcheck: test: ["CMD", "grpc_health_probe", "-addr=localhost:${SCANNER_SERVICE_PORT:-50052}"] interval: 15s timeout: 5s start_period: 5s retries: 3 deploy: resources: limits: cpus: "${TAMGA_SCANNER_SERVICE_CPUS:-2}" memory: ${TAMGA_SCANNER_SERVICE_MEMORY:-512m} reservations: memory: 64m proxy: profiles: ["full"] build: context: ../proxy dockerfile: Dockerfile # Not published: the edge is. Reach it through https://localhost:8443. expose: - "${TAMGA_PROXY_PORT:-8443}" # Load the operator's proxy settings as service environment, not only as # Compose interpolation inputs. Paths are relative to this Compose file. # Set TAMGA_PROXY_ENV_FILE when --env-file names another file. Without # the file (CI, a first look with --env-file .env.example) the proxy # runs on the values Compose sets below. env_file: - path: ${TAMGA_PROXY_ENV_FILE:-../.env} required: false environment: - TAMGA_PROXY_PORT=${TAMGA_PROXY_PORT:-8443} - TAMGA_POLICY_PATH=/app/tamga-policy.yaml - TAMGA_ANALYZER_ADDR=${TAMGA_ANALYZER_ADDR:-analyzer:${TAMGA_GRPC_PORT:-50051}} # The analyzer's HTTP API, where the PDF reports are generated. - TAMGA_ANALYZER_HTTP_URL=${TAMGA_ANALYZER_HTTP_URL:-http://analyzer:${TAMGA_ANALYZER_PORT:-8444}} # Off unless set to true: copy the text of forwarded requests to the # analyzer for analysis after the decision. A blocked request's text # is never copied. - TAMGA_ANALYZER_SEND_CONTENT=${TAMGA_ANALYZER_SEND_CONTENT:-false} # Inline prompt-injection classifier. Empty unless the classifier # profile is in use: set TAMGA_CLASSIFIER_ADDR=classifier:50052 in .env. - TAMGA_CLASSIFIER_ADDR=${TAMGA_CLASSIFIER_ADDR:-} # One token per internal service, the same value the service is given # (openssl rand -hex 32). With TAMGA_REQUIRE_SERVICE_TOKENS on, the # proxy does not start when a service it has an address for has no # token: the analyzer always, the classifier and the scanner service # when their addresses are set. - TAMGA_ANALYZER_TOKEN=${TAMGA_ANALYZER_TOKEN:-} - TAMGA_CLASSIFIER_TOKEN=${TAMGA_CLASSIFIER_TOKEN:-} - TAMGA_SCANNER_SERVICE_TOKEN=${TAMGA_SCANNER_SERVICE_TOKEN:-} - TAMGA_REQUIRE_SERVICE_TOKENS=${TAMGA_REQUIRE_SERVICE_TOKENS:-true} # Every caller reaches the proxy through the edge: trust its address # for X-Forwarded-For, or every caller without a key shares one # rate-limit bucket and TAMGA_IP_ALLOWLIST sees only the edge. - TAMGA_TRUSTED_PROXIES=${TAMGA_TRUSTED_PROXIES:-${TAMGA_EDGE_ADDR:-10.213.98.2}} - TAMGA_DB_URL=${TAMGA_DB_URL:-postgresql://${POSTGRES_USER:-tamga}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-tamga}} - REDIS_URL=${REDIS_URL:-redis://:${REDIS_PASSWORD}@redis:6379} - TAMGA_NATS_URL=${TAMGA_NATS_URL:-nats://nats:4222} - TAMGA_SCANNER_SERVICE_ADDR=${TAMGA_SCANNER_SERVICE_ADDR:-} - TAMGA_ADMIN_KEY=${TAMGA_ADMIN_KEY} # Demo / test mode: answer with a canned response instead of calling the # provider. Scanning and policy still run. Off unless set in .env. - TAMGA_MOCK_UPSTREAM=${TAMGA_MOCK_UPSTREAM:-false} - TAMGA_ORG_ID=${TAMGA_ORG_ID:-} - TAMGA_CLERK_SECRET_KEY=${TAMGA_CLERK_SECRET_KEY:-} # Sessions and GitHub sign-in. All empty unless set in .env, and an # empty value is the same as an unset one: no sessions, and the admin # key and API keys are the only way in. TAMGA_JWT_SECRET signs the # session tokens (openssl rand -hex 32); GitHub sign-in needs it and # the three values of a GitHub OAuth app. - TAMGA_JWT_SECRET=${TAMGA_JWT_SECRET:-} - TAMGA_GITHUB_CLIENT_ID=${TAMGA_GITHUB_CLIENT_ID:-} - TAMGA_GITHUB_CLIENT_SECRET=${TAMGA_GITHUB_CLIENT_SECRET:-} - TAMGA_GITHUB_OAUTH_CALLBACK_URL=${TAMGA_GITHUB_OAUTH_CALLBACK_URL:-} # Key for the hashes of finding matches in the request log, used when # the policy has data.hash_findings on (openssl rand -base64 32). # Empty, and without TAMGA_VAULT_KEY, those matches are not stored. - TAMGA_FINDING_HASH_KEY=${TAMGA_FINDING_HASH_KEY:-} # Empty refuses a signed-in account nobody gave a role; "viewer" lets # it in as a viewer. The proxy does not start on any other value. - TAMGA_SSO_DEFAULT_ROLE=${TAMGA_SSO_DEFAULT_ROLE:-} - TAMGA_CORS_ORIGIN=${TAMGA_CORS_ORIGIN:-https://localhost:3000} # Exact-match allowlist: the console's address as the browser sees it, # through the edge. - TAMGA_CORS_ORIGINS=${TAMGA_CORS_ORIGINS:-https://localhost:3000} - TAMGA_OTLP_ENDPOINT=${TAMGA_OTLP_ENDPOINT:-http://jaeger:4318} - TAMGA_OTEL_SAMPLE_RATE=${TAMGA_OTEL_SAMPLE_RATE:-0.05} - TAMGA_TRACE_UI_URL=${TAMGA_TRACE_UI_URL:-http://localhost:16686} - TAMGA_RETENTION_ENABLED=${TAMGA_RETENTION_ENABLED:-true} depends_on: postgres: condition: service_healthy analyzer: condition: service_healthy volumes: # Read-only by default: the console then shows the policy and cannot # change it. TAMGA_POLICY_MOUNT=rw lets "Save and apply" rewrite this # file on the host, comments included. - ../proxy/tamga-policy.yaml:/app/tamga-policy.yaml:${TAMGA_POLICY_MOUNT:-ro} networks: - internal - edge restart: unless-stopped healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${TAMGA_PROXY_PORT:-8443}/health || exit 1"] interval: 15s timeout: 3s start_period: 5s retries: 3 # The language check holds 53 MiB once a policy turns it on; the rest is # request bodies in flight (up to TAMGA_MAX_BODY_BYTES each) and the # caches. The latency figures in the README were measured without limits. deploy: resources: limits: cpus: "${TAMGA_PROXY_CPUS:-2}" memory: ${TAMGA_PROXY_MEMORY:-1g} reservations: memory: 256m # The proxy on its own: no database, Redis, NATS, analyzer, Jaeger or # console, and none of their passwords are needed. Start it with # docker compose --env-file .env -f deploy/docker-compose.yml --profile minimal up -d # Scanning, policy and blocking work as in the full stack. The request log, # the console, keys kept across restarts and the PDF reports do not; see # docs/operations.md, "Proxy only". There is no edge: the port is the # proxy's own, plain HTTP unless TAMGA_TLS_CERT and TAMGA_TLS_KEY name a # certificate mounted into the container. proxy-minimal: profiles: ["minimal"] build: context: ../proxy dockerfile: Dockerfile ports: - "${TAMGA_PROXY_BIND:-0.0.0.0}:${TAMGA_PROXY_PORT:-8443}:${TAMGA_PROXY_PORT:-8443}" env_file: - path: ${TAMGA_PROXY_ENV_FILE:-../.env} required: false environment: - TAMGA_PROXY_PORT=${TAMGA_PROXY_PORT:-8443} - TAMGA_POLICY_PATH=/app/tamga-policy.yaml # Keep the standalone profile standalone even when the shared env file # contains the full stack's host-style connection examples. - TAMGA_ANALYZER_ADDR= - TAMGA_ANALYZER_HTTP_URL= - TAMGA_ANALYZER_SEND_CONTENT=false - TAMGA_CLASSIFIER_ADDR= - TAMGA_DB_URL= - REDIS_URL= - TAMGA_NATS_URL= - TAMGA_SCANNER_SERVICE_ADDR= # No internal service to authenticate to. - TAMGA_REQUIRE_SERVICE_TOKENS=false - TAMGA_OTEL_ENABLED=false - TAMGA_OTEL_ENDPOINT= - TAMGA_OTLP_ENDPOINT= - TAMGA_TRACE_UI_URL= - TAMGA_RETENTION_ENABLED=false # Empty means every management API call is refused with 401. The # proxy routes (/v1, /anthropic, ...) do not need it. - TAMGA_ADMIN_KEY=${TAMGA_ADMIN_KEY:-} - TAMGA_MOCK_UPSTREAM=${TAMGA_MOCK_UPSTREAM:-false} volumes: - ../proxy/tamga-policy.yaml:/app/tamga-policy.yaml:${TAMGA_POLICY_MOUNT:-ro} restart: unless-stopped healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${TAMGA_PROXY_PORT:-8443}/health || exit 1"] interval: 15s timeout: 3s start_period: 5s retries: 3 deploy: resources: limits: cpus: "${TAMGA_PROXY_CPUS:-2}" memory: ${TAMGA_PROXY_MEMORY:-1g} reservations: memory: 256m analyzer: profiles: ["full"] build: context: ../analyzer dockerfile: Dockerfile expose: - "${TAMGA_ANALYZER_PORT:-8444}" # HTTP health + reports (FastAPI) - "${TAMGA_GRPC_PORT:-50051}" # gRPC deep analysis (protobuf) command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "${TAMGA_ANALYZER_PORT:-8444}"] environment: - TAMGA_GRPC_PORT=${TAMGA_GRPC_PORT:-50051} # Keep two analyzer workers so the proxy and support services retain # CPU on the shared Compose host. Direct-process default: up to four. - TAMGA_ANALYZER_WORKERS=${TAMGA_ANALYZER_WORKERS:-2} # The LLM judge sends request text to Anthropic. It is off unless # TAMGA_LLM_JUDGE=anthropic is set; the key alone does not turn it on. - TAMGA_LLM_JUDGE=${TAMGA_LLM_JUDGE:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY} - DATABASE_URL=postgresql://${POSTGRES_USER:-tamga}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-tamga} - TAMGA_CUSTOM_SCANNERS_DIR=${TAMGA_CUSTOM_SCANNERS_DIR:-/custom_scanners} # The proxy's token, for the gRPC port and the HTTP API (every path # but /health). Required while TAMGA_REQUIRE_SERVICE_TOKENS is on. - TAMGA_ANALYZER_TOKEN=${TAMGA_ANALYZER_TOKEN:-} - TAMGA_REQUIRE_SERVICE_TOKENS=${TAMGA_REQUIRE_SERVICE_TOKENS:-true} depends_on: postgres: condition: service_healthy networks: - internal restart: unless-stopped healthcheck: test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:${TAMGA_ANALYZER_PORT:-8444}/health')\" || exit 1"] interval: 30s timeout: 5s start_period: 60s retries: 3 # spaCy and Presidio in each of the TAMGA_ANALYZER_WORKERS processes; # one CPU per worker. deploy: resources: limits: cpus: "${TAMGA_ANALYZER_CPUS:-2}" memory: ${TAMGA_ANALYZER_MEMORY:-2g} reservations: memory: 512m # Optional: the inline prompt-injection classifier. Start it with the full # stack: # docker compose --profile full --profile classifier up -d # or set COMPOSE_PROFILES=full,classifier in .env # after downloading a model (docs/operations.md). No model is shipped; with # an empty directory the service runs and reports itself unavailable. # The second, optional directory holds the model for banned topics # (docs/operations.md, "Banned topics"). Left empty, topic calls answer # UNAVAILABLE and injection scoring is unaffected. # The third, optional directory holds a candidate injection model scored in # shadow next to the one in service (docs/operations.md, "Shadow mode"). # Left empty, no shadow runs and nothing else changes. classifier: profiles: ["classifier"] build: context: ../classifier dockerfile: Dockerfile expose: - "${TAMGA_CLASSIFIER_PORT:-50052}" # gRPC environment: - TAMGA_CLASSIFIER_PORT=${TAMGA_CLASSIFIER_PORT:-50052} - TAMGA_CLASSIFIER_MODEL_DIR=/models/classifier - TAMGA_CLASSIFIER_MODEL_NAME=${TAMGA_CLASSIFIER_MODEL_NAME:-prompt-injection-guard-small} - TAMGA_CLASSIFIER_THREADS=${TAMGA_CLASSIFIER_THREADS:-0} - TAMGA_CLASSIFIER_WINDOW=${TAMGA_CLASSIFIER_WINDOW:-512} - TAMGA_CLASSIFIER_OVERLAP=${TAMGA_CLASSIFIER_OVERLAP:-64} - TAMGA_CLASSIFIER_MAX_WINDOWS=${TAMGA_CLASSIFIER_MAX_WINDOWS:-16} - TAMGA_TOPICS_MODEL_DIR=/models/topics - TAMGA_TOPICS_MODEL_NAME=${TAMGA_TOPICS_MODEL_NAME:-mdeberta-v3-base-xnli-multilingual-nli-2mil7} - TAMGA_TOPICS_THREADS=${TAMGA_TOPICS_THREADS:-0} - TAMGA_CLASSIFIER_SHADOW_MODEL_DIR=/models/shadow - TAMGA_CLASSIFIER_SHADOW_MODEL_NAME=${TAMGA_CLASSIFIER_SHADOW_MODEL_NAME:-} - TAMGA_CLASSIFIER_SHADOW_THREADS=${TAMGA_CLASSIFIER_SHADOW_THREADS:-0} - TAMGA_CLASSIFIER_SHADOW_WAIT_MS=${TAMGA_CLASSIFIER_SHADOW_WAIT_MS:-20} # The proxy's token for this service; the container's health probe # sends it too. Required while TAMGA_REQUIRE_SERVICE_TOKENS is on. - TAMGA_CLASSIFIER_TOKEN=${TAMGA_CLASSIFIER_TOKEN:-} - TAMGA_REQUIRE_SERVICE_TOKENS=${TAMGA_REQUIRE_SERVICE_TOKENS:-true} volumes: - ${TAMGA_CLASSIFIER_MODEL_PATH:-../models/prompt-injection-guard-small}:/models/classifier:ro - ${TAMGA_TOPICS_MODEL_PATH:-../models/mdeberta-nli}:/models/topics:ro - ${TAMGA_CLASSIFIER_SHADOW_MODEL_PATH:-../models/shadow}:/models/shadow:ro networks: - internal restart: unless-stopped # The three int8 models are 268 MB (injection), 339 MB (topics) and as # much again for a shadow. With a CPU limit, keep TAMGA_CLASSIFIER_THREADS # plus TAMGA_CLASSIFIER_SHADOW_THREADS at or under TAMGA_CLASSIFIER_CPUS: # 0 lets onnxruntime start a thread per host core. deploy: resources: limits: cpus: "${TAMGA_CLASSIFIER_CPUS:-2}" memory: ${TAMGA_CLASSIFIER_MEMORY:-3g} reservations: memory: 1g dashboard: profiles: ["full"] build: context: ../dashboard dockerfile: Dockerfile # Compiled into the page: the address the BROWSER reaches the proxy # at, not the proxy's name on the compose network. Changing either # needs a rebuild (docker compose build dashboard). args: - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-https://localhost:8443} - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-https://tamgaproxy.com} - NEXT_PUBLIC_MARKETING_URL=${NEXT_PUBLIC_MARKETING_URL:-https://tamgaproxy.com} # Not published: the edge is. Reach it through https://localhost:3000. expose: - "3000" depends_on: - proxy # The console's server needs nothing on the internal network: the # browser calls the proxy itself, at NEXT_PUBLIC_API_URL. networks: - edge restart: unless-stopped deploy: resources: limits: cpus: "${TAMGA_DASHBOARD_CPUS:-1}" memory: ${TAMGA_DASHBOARD_MEMORY:-512m} reservations: memory: 128m postgres: profiles: ["full"] image: postgres:16-alpine # TODO: pin by digest (see IMAGES above) expose: - "5432" environment: - POSTGRES_USER=${POSTGRES_USER:-tamga} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB:-tamga} volumes: - pgdata:/var/lib/postgresql/data - ./migrations/001_init.up.sql:/docker-entrypoint-initdb.d/001_init.sql:ro - ./migrations/002_indexes.up.sql:/docker-entrypoint-initdb.d/002_indexes.sql:ro - ./migrations/003_output_scan.up.sql:/docker-entrypoint-initdb.d/003_output_scan.sql:ro - ./migrations/004_audit_policy.up.sql:/docker-entrypoint-initdb.d/004_audit_policy.sql:ro - ./migrations/005_retention_run_log.up.sql:/docker-entrypoint-initdb.d/005_retention_run_log.sql:ro - ./migrations/006_model_metadata.up.sql:/docker-entrypoint-initdb.d/006_model_metadata.sql:ro - ./migrations/007_analyzer_findings.up.sql:/docker-entrypoint-initdb.d/007_analyzer_findings.sql:ro - ./migrations/008_model_pricing.up.sql:/docker-entrypoint-initdb.d/008_model_pricing.sql:ro - ./migrations/009_missing_models.up.sql:/docker-entrypoint-initdb.d/009_missing_models.sql:ro - ./migrations/010_event_outbox.up.sql:/docker-entrypoint-initdb.d/010_event_outbox.sql:ro - ./migrations/011_tenant_rls.up.sql:/docker-entrypoint-initdb.d/011_tenant_rls.sql:ro - ./migrations/012_incident_lifecycle.up.sql:/docker-entrypoint-initdb.d/012_incident_lifecycle.sql:ro - ./migrations/013_saved_hunts.up.sql:/docker-entrypoint-initdb.d/013_saved_hunts.sql:ro - ./migrations/014_incident_lifecycle_align.up.sql:/docker-entrypoint-initdb.d/014_incident_lifecycle_align.sql:ro - ./migrations/015_virtual_keys.up.sql:/docker-entrypoint-initdb.d/015_virtual_keys.sql:ro - ./migrations/016_stored_documents.up.sql:/docker-entrypoint-initdb.d/016_stored_documents.sql:ro - ./migrations/017_request_record.up.sql:/docker-entrypoint-initdb.d/017_request_record.sql:ro - ./migrations/018_drop_tenant_rls.up.sql:/docker-entrypoint-initdb.d/018_drop_tenant_rls.sql:ro - ./migrations/019_drop_unused_tables.up.sql:/docker-entrypoint-initdb.d/019_drop_unused_tables.sql:ro - ./migrations/020_route_pool.up.sql:/docker-entrypoint-initdb.d/020_route_pool.sql:ro - ./migrations/021_classifier_scores.up.sql:/docker-entrypoint-initdb.d/021_classifier_scores.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tamga}"] interval: 5s timeout: 5s retries: 5 networks: - internal restart: unless-stopped deploy: resources: limits: cpus: "${TAMGA_POSTGRES_CPUS:-2}" memory: ${TAMGA_POSTGRES_MEMORY:-1g} reservations: memory: 256m redis: profiles: ["full"] image: redis:7-alpine # TODO: pin by digest (see IMAGES above) command: redis-server --requirepass ${REDIS_PASSWORD} --save 60 1 --loglevel warning expose: - "6379" volumes: - redisdata:/data networks: - internal restart: unless-stopped healthcheck: test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD}' ping | grep -q PONG"] interval: 5s timeout: 5s retries: 5 deploy: resources: limits: cpus: "${TAMGA_REDIS_CPUS:-1}" memory: ${TAMGA_REDIS_MEMORY:-512m} reservations: memory: 64m jaeger: profiles: ["full"] image: jaegertracing/all-in-one:1.76.0 # TODO: pin by digest (see IMAGES above) expose: - "4318" ports: - "${TAMGA_OBSERVABILITY_BIND:-127.0.0.1}:16686:16686" environment: COLLECTOR_OTLP_ENABLED: "true" # Traces are kept in memory. Without a bound they grow until the # memory limit stops the container. MEMORY_MAX_TRACES: "${TAMGA_JAEGER_MAX_TRACES:-50000}" networks: - internal restart: unless-stopped deploy: resources: limits: cpus: "${TAMGA_JAEGER_CPUS:-1}" memory: ${TAMGA_JAEGER_MEMORY:-1g} reservations: memory: 128m nats: profiles: ["full"] image: nats:2.15-alpine # TODO: pin by digest (see IMAGES above) command: ["-js", "-m", "8222"] expose: - "4222" - "8222" ports: - "${TAMGA_OBSERVABILITY_BIND:-127.0.0.1}:8222:8222" networks: - internal restart: unless-stopped deploy: resources: limits: cpus: "${TAMGA_NATS_CPUS:-1}" memory: ${TAMGA_NATS_MEMORY:-512m} reservations: memory: 64m volumes: pgdata: redisdata: # The edge's certificates and its local CA. Keep it: a new CA means every # client has to trust a new root. edgedata: edgeconfig: networks: internal: driver: bridge # The edge and the two services behind it. The subnet is fixed so the # edge's address is known (TAMGA_EDGE_ADDR, trusted by the proxy); the # other two take addresses from ip_range. Change all three together if # the subnet collides with one of the host's. edge: driver: bridge ipam: config: - subnet: ${TAMGA_EDGE_SUBNET:-10.213.98.0/28} ip_range: ${TAMGA_EDGE_IP_RANGE:-10.213.98.8/29}