Ctrl K
ComunidadGuías…

docker-compose.demo.yml

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

Archivo original ↗Preguntar a KiBot
# soc-ai PUBLIC DEMO stack — one container, read-only replay. NOT for real use.
#
# This is the local twin of the hosted demo (see render.yaml): the same image
# as docker-compose.yml, but started through docker/demo-entrypoint.sh, which
# runs the bundled mock Elasticsearch/LLM endpoint on 127.0.0.1:9200 (inside
# the container, never published) and then the app with SOC_AI_DEMO=true.
# The app seeds the sanitized soc_ai/demo/fixtures.json at startup (fail-soft:
# absent fixtures boot an empty demo), blocks every mutating request except
# the two replay triggers, and refuses all non-loopback egress.
#
# TLS: NONE in this container, deliberately. The hosted demo sits behind the
# platform edge (which terminates TLS and forwards plain HTTP), so the demo
# entrypoint starts uvicorn WITHOUT the --ssl-* flags the production compose
# file relies on. Locally that means plain http://127.0.0.1:8080 — bound to
# loopback by default, and it must stay that way (auth is pinned off below).
#
# Usage:
#   docker compose -f docker-compose.demo.yml up --build
#   → http://127.0.0.1:8080/app/
#
# NO .env, NO secrets: every value below is a loopback URL or an inert
# placeholder that the Settings schema requires to boot. Nothing sensitive
# exists in the demo environment — by spec.

services:

  soc-ai-demo:
    image: ghcr.io/nuk3s/soc-ai:${SOC_AI_IMAGE_TAG:-latest}
    build:
      context: .
      dockerfile: Dockerfile
    container_name: soc-ai-demo

    # The demo entrypoint replaces the image's TLS-serving default CMD.
    entrypoint: ["/opt/soc-ai/docker/demo-entrypoint.sh"]

    # Loopback publish by default — API_AUTH_REQUIRED=false makes this stack
    # unauthenticated, so never bind it wider on a box you share. The hosted
    # demo gets its public exposure through the platform edge, not this port.
    ports:
      - "${SOC_AI_DEMO_BIND:-127.0.0.1}:${SOC_AI_DEMO_PORT:-8080}:8080"

    # environment (NOT env_file) on purpose: the demo must boot with no .env.
    environment:
      # Master switch — env-only by design (soc_ai/config.py), so it can never
      # be flipped from the UI.
      SOC_AI_DEMO: "true"
      # CARRIED DECISION: the demo read-only middleware blocks POST /login,
      # and SOC_AI_DEMO does NOT force auth off — pin it off here so visitors
      # land straight in the (read-only) UI instead of an unusable login form.
      API_AUTH_REQUIRED: "false"
      # Public-facing flood backpressure (default is 1200 for real deploys).
      # The SPA fires ~10-15 API calls per page view, so this must clear a real
      # visitor's navigation while still catching bots/scrapers — 30 throttled a
      # human to ~2 clicks/min (dogfood finding). Override for trusted/local use:
      # SOC_AI_DEMO_RATE_LIMIT=600 podman-compose -f docker-compose.demo.yml up.
      API_RATE_LIMIT_PER_MIN: "${SOC_AI_DEMO_RATE_LIMIT:-120}"
      # Everything below either points at the in-container mock (the demo
      # egress guard refuses any non-loopback client) or is an inert
      # placeholder Settings requires. No real endpoint, credential, or key.
      ES_HOSTS: "http://127.0.0.1:9200"
      SO_HOST: "http://127.0.0.1:9200"
      SO_USERNAME: "demo"
      SO_PASSWORD: "demo-placeholder-unused"
      LITELLM_BASE_URL: "http://127.0.0.1:9200"

    # Plain-HTTP healthcheck — overrides the image's HTTPS one, which matches
    # the production CMD (TLS on 8443), not the demo entrypoint (HTTP on 8080).
    # Two-target: probe the bundled mock ES (:9200) as well as the app, because
    # /healthz is pure app liveness and would stay green with a dead mock behind
    # it (empty grids). The entrypoint also process-supervises the mock; this
    # additionally catches an alive-but-wedged mock.
    healthcheck:
      test:
        - CMD-SHELL
        - "curl -fs http://127.0.0.1:9200/ && curl -fs http://127.0.0.1:8080/healthz"
      interval: 30s
      timeout: 10s
      start_period: 60s
      retries: 3

    restart: unless-stopped

    # No volumes: the demo is stateless on purpose. The SQLite store lives in
    # the container filesystem and is re-seeded from the packaged fixtures on
    # every fresh container — exactly like the hosted free tier (ephemeral FS).

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

    # Same hardening as the production compose file.
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL

    # Sized to the hosting target (Render free web service = 512 MB).
    deploy:
      resources:
        limits:
          memory: 512M