Ctrl K
ComunidadGuías…

docker-compose.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# soc-ai compose stack.
#
# Usage:
#   docker compose up -d                              # build from source + start
#   SOC_AI_IMAGE_TAG=1.2.9 docker compose pull soc-ai && docker compose up -d
#     # run a pinned published GHCR image — never pull bare :latest
#
# soc-ai talks to a LiteLLM gateway you run separately (most setups put it on a
# GPU host). The RAG/reranker engine and its vector store arrive with the v1.1
# "heavy" deployment; they are not part of this stack.
#
# Before starting:
#   1. Copy .env.example → .env and populate all required values.
#   2. Generate a self-signed (or CA-signed) cert pair:
#        mkdir -p ./certs
#        openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
#          -subj "/CN=soc-ai" -keyout ./certs/key.pem -out ./certs/cert.pem
#   3. (Optional) Run `soc-ai blocklists refresh` once to seed enrichment data.

services:

  soc-ai:
    # ── Image: published release (pull) or local build ───────────────────────
    # With both `image:` and `build:` declared, plain `docker compose up` still
    # BUILDS from source when the image isn't in the local store (Compose
    # prefers `build:` over pulling) — so the existing setup.sh / `up --build`
    # flow is unchanged. To run the prebuilt release image instead, pull it
    # explicitly first, PINNED to a release (never bare :latest — an
    # unaudited moving target):
    #     SOC_AI_IMAGE_TAG=1.2.9 docker compose pull soc-ai && docker compose up -d
    # (or `./setup.sh --prebuilt`, which pins it for you).
    image: ghcr.io/nuk3s/soc-ai:${SOC_AI_IMAGE_TAG:-latest}
    build:
      context: .
      dockerfile: Dockerfile
      args:
        # Stamps the built image with the commit it came from, so a quality
        # regression can be attributed to a code change. Empty unless the
        # builder exports it — `export SOC_AI_COMMIT=$(git rev-parse HEAD)`
        # before `docker compose build` — and empty is recorded as "unknown",
        # never guessed.
        SOC_AI_COMMIT: ${SOC_AI_COMMIT:-}
    container_name: soc-ai

    # ── Port ─────────────────────────────────────────────────────────────────
    # uvicorn terminates TLS on 8443 on the direct path. The caddy service
    # below puts Caddy in front for the proxy path.
    # Published on all interfaces by default. Set SOC_AI_BIND=127.0.0.1 to bind
    # loopback only (e.g. behind a reverse proxy on the same host). NOTE: Docker's
    # publish rules bypass firewalld — `firewall-cmd --list-ports` won't show this
    # port; restrict it here (SOC_AI_BIND) or via the DOCKER-USER chain, not the
    # host firewall zones.
    ports:
      - "${SOC_AI_BIND:-0.0.0.0}:${SOC_AI_PORT:-8443}:8443"

    # ── Environment & secrets ─────────────────────────────────────────────────
    # .env is bind-mounted read-only so secrets never bake into an image layer.
    # pydantic-settings reads it from the working directory (/opt/soc-ai).
    env_file:
      - path: .env
        required: true   # fail fast if the operator forgot to create it

    # ── Mounts ───────────────────────────────────────────────────────────────
    # NOTE: mounted files must be READABLE BY UID 1000 (the in-container soc-ai
    # user). `openssl` writes the key 0600 owned by you — `chmod 644 ./certs/*`.
    # The `,Z` suffix relabels each bind mount for the container on SELinux hosts
    # (Fedora/RHEL/Rocky/podman); without it the container gets EACCES even at
    # mode 644. On non-SELinux hosts (Ubuntu/Debian) `,Z` is a harmless no-op, so
    # it ships on by default.
    volumes:
      # TLS certificate — read-only; generate once, mount permanently.
      # Generate with: openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
      #   -subj "/CN=soc-ai" -keyout ./certs/key.pem -out ./certs/cert.pem
      - ./certs/cert.pem:/etc/soc-ai/cert.pem:ro,Z

      # TLS private key — read-only; readable by uid 1000 (see note above).
      - ./certs/key.pem:/etc/soc-ai/key.pem:ro,Z

      # PCAP fetch SSH key (OPTIONAL — only for t_get_pcap). Uncomment + provide
      # the de-privileged sensor key, then set SO_SSH_KEY=/etc/soc-ai/so_pcap in
      # .env. Read-only; readable by uid 1000.
      # - ./certs/so_pcap:/etc/soc-ai/so_pcap:ro,Z

      # SQLite database + session store.  Named volume so data survives
      # container replacement (image updates, `docker compose down`).
      # Maps to SOC_AI_DATA_DIR=/var/lib/soc-ai/data (set in .env or default).
      - soc_ai_data:/var/lib/soc-ai/data

      # Blocklist cache (URLhaus, Feodo, Tor, etc.) — optional but recommended.
      # Seed with: docker compose run --rm soc-ai soc-ai blocklists refresh
      - soc_ai_blocklists:/var/lib/soc-ai/blocklists

      # MaxMind GeoLite2 .mmdb files. Optional. No command downloads them. Copy
      # GeoLite2-City.mmdb and GeoLite2-ASN.mmdb into this volume by hand, then
      # restart. See docs/BLOCKLISTS.md, "MaxMind GeoIP".
      - soc_ai_maxmind:/var/lib/soc-ai/maxmind

      # Cloud-provider prefix JSON (AWS/GCP/Azure/Cloudflare) — optional.
      # Seed with: docker compose run --rm soc-ai soc-ai blocklists refresh
      - soc_ai_cloud_prefixes:/var/lib/soc-ai/cloud_prefixes

      # Nightly quality-eval artifacts: the per-alert bundles and oracle
      # critiques behind every point on the dashboard's Quality card. NOT
      # optional if you want the regression alarm to be actionable — the
      # critiques are the only evidence for or against an alarm, and without
      # this volume they live in the container filesystem, so every recreate
      # (image update, `docker compose up -d`) deletes them.
      - soc_ai_evals:/var/lib/soc-ai/evals

    # ── Liveness check ───────────────────────────────────────────────────────
    # Mirrors the HEALTHCHECK in the Dockerfile. It tries HTTPS, then HTTP, so
    # it passes on the direct path and behind the caddy service.
    #
    # LIVENESS, not health: a green container here means this process answered,
    # nothing more. It probes no dependency and must not, or an outage upstream
    # would become a restart loop down here. For the verdict, run
    # `docker compose exec soc-ai python -m soc_ai doctor`.
    healthcheck:
      test: ["CMD-SHELL", "curl -fsk https://127.0.0.1:8443/healthz || curl -fs http://127.0.0.1:8443/healthz"]
      interval: 30s
      timeout: 10s
      start_period: 60s
      retries: 3

    restart: unless-stopped

    # ── Log rotation ──────────────────────────────────────────────────────────
    # The always-on triage loop + the 30s healthcheck curl write to the default
    # json-file driver, which grows UNBOUNDED without a cap — the failure mode
    # most likely to fill the disk over months. Cap at 5×50m (250m ceiling).
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "5"

    # ── Hardening ─────────────────────────────────────────────────────────────
    # The app runs as non-root (UID 1000) and binds 8443 (>1024), so it needs no
    # Linux capabilities: drop them all and block privilege escalation (setuid).
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL

    # ── Resource hints (adjust to your hardware) ──────────────────────────────
    # The app is primarily I/O-bound (ES queries + LLM calls); these are soft
    # limits that prevent a runaway investigation loop from starving the host.
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 256M

  caddy:
    # ── The proxy path: Caddy in front of soc-ai ─────────────────────────────
    # Caddy terminates TLS and renews its certificate. soc-ai serves plain HTTP
    # inside the compose network. The service sits under the "proxy" profile,
    # so a plain `docker compose up -d` does not start it. COMPOSE_PROFILES=proxy
    # in .env turns the profile on for every compose command, so a later
    # `docker compose up -d` keeps Caddy. `scripts/tls-proxy.sh enable <domain>`
    # writes that line and the other proxy settings. See docs/DOCKER.md, TLS paths.
    profiles: ["proxy"]
    # Digest-pinned, the same rule as the Dockerfile base images. Tag: 2.11.4.
    image: caddy:2.11.4@sha256:0c994536bddb66445885237f1a5dcc1916bccea922661c76b4e9fc24061f9b52
    container_name: soc-ai-caddy
    restart: unless-stopped
    depends_on:
      - soc-ai
    # Two variables from the project .env, and none of soc-ai's secrets. Both
    # use the default form: compose interpolates the whole file before it
    # filters profiles, so a required form would break the direct path.
    # SOC_AI_DOMAIN names the site. SOC_AI_CADDY_TLS holds the tls directive:
    # empty for automatic HTTPS from Let's Encrypt, "tls internal" for Caddy's
    # own CA, or "tls /certs/proxy-cert.pem /certs/proxy-key.pem" for your files.
    environment:
      SOC_AI_DOMAIN: "${SOC_AI_DOMAIN:-}"
      SOC_AI_CADDY_TLS: "${SOC_AI_CADDY_TLS:-}"
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
      # Your certificate and key, for SOC_AI_CADDY_TLS="tls /certs/proxy-cert.pem
      # /certs/proxy-key.pem". The script copies them in. ./certs exists before
      # the first start, or Docker creates it as root.
      - ./certs:/certs:ro,Z
      - caddy_data:/data
      - caddy_config:/config
    security_opt:
      - no-new-privileges:true
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "3"

  postgres:
    # ── The PostgreSQL store (optional) ──────────────────────────────────────
    # soc-ai keeps its store in SQLite by default. SQLite has one writer, and
    # at corporate scale the scheduler, the sweeps and the console contend for
    # it. This service sits under the "postgres" profile, so a plain
    # `docker compose up -d` does not start it. COMPOSE_PROFILES=postgres in
    # .env turns the profile on for every compose command. setup.sh asks, then
    # writes that line, SOC_AI_POSTGRES_PASSWORD and SOC_AI_DATABASE_URL.
    # soc-ai waits up to 60 s at start for the server to accept connections.
    # See docs/DOCKER.md, PostgreSQL.
    profiles: ["postgres"]
    # Digest-pinned, the same rule as the Dockerfile base images. Tag: 17.6.
    image: postgres:17.6@sha256:00bc86618629af00d2937fdc5a5d63db3ff8450acf52f0636ec813c7f4902929
    container_name: soc-ai-postgres
    restart: unless-stopped
    # The default form, as for caddy: compose interpolates the whole file
    # before it filters profiles. An empty password stops the server at its
    # first start, so the profile cannot run without one.
    environment:
      POSTGRES_USER: soc_ai
      POSTGRES_DB: soc_ai
      POSTGRES_PASSWORD: "${SOC_AI_POSTGRES_PASSWORD:-}"
    # No published port. soc-ai reaches the server on the compose network as
    # postgres:5432, and nothing else needs to.
    volumes:
      - soc_ai_postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U soc_ai -d soc_ai"]
      interval: 10s
      timeout: 5s
      start_period: 30s
      retries: 6
    security_opt:
      - no-new-privileges:true
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "3"

# ── Named volumes ─────────────────────────────────────────────────────────────
volumes:
  # Persistent application data (SQLite DB + session store).
  soc_ai_data:

  # Enrichment caches — safe to recreate; re-seed with `soc-ai blocklists refresh`.
  soc_ai_blocklists:
  soc_ai_cloud_prefixes:

  # GeoLite2 .mmdb files. Safe to recreate. Copy them back in by hand.
  # See docs/BLOCKLISTS.md, "MaxMind GeoIP".
  soc_ai_maxmind:

  # Nightly quality-eval artifacts — evidence, not cache: nothing regenerates
  # a deleted bundle, and the trend row that points at it outlives it by
  # ~3 months.
  soc_ai_evals:

  # The PostgreSQL store, when the "postgres" profile is on. This is the data
  # itself: back it up with pg_dump, and never recreate it to fix a start.
  soc_ai_postgres:

  # Caddy keeps its certificates and its CA here. Keep it across restarts.
  caddy_data:
  # Caddy's own runtime config. Safe to recreate.
  caddy_config: