Ctrl K
ComunidadGuías…

docker-compose.yml

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

Archivo original ↗Preguntar a KiBot
services:
  backend:
    restart: unless-stopped
    # Published image of the checked-out version (KT-1468): `make start` pulls
    # it instead of compiling the 715 k-line backend crate (19 GB peak) on the
    # user's machine. `build:` stays for `make start-build` and for a VERSION
    # with no published image yet. The tag below is bumped with VERSION (`make bump`).
    image: ghcr.io/docroms/kronn-backend:${KRONN_IMAGE_TAG:-0.15.2}
    build:
      context: ./backend
      dockerfile: Dockerfile
      args:
        CARGO_PROFILE: ${CARGO_PROFILE:-fast}
        APP_UID: ${KRONN_HOST_UID:-1000}
        APP_GID: ${KRONN_HOST_GID:-1000}
    # The host's Docker socket (and its group) is NOT mounted by default: any
    # process in this container — every agent — could use it to become root on
    # the host. `KRONN_DOCKER_SOCKET=1` in .env adds both through the generated
    # docker-compose.override.yml (KT-979).
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Linux: resolve host.docker.internal → host IP (macOS/Windows have this built-in)
    expose:
      - "3140"
    volumes:
      - kronn-data:/data
      # Run artifacts (KT-1585): outside /data, which agents cannot read, and a
      # volume so a run resumed after a restart still finds its files.
      - kronn-run-artifacts:/run-artifacts
      - ${HOME}:/host-home:ro
      - ${KRONN_REPOS_DIR:-${HOME}/Repositories}:/host-home/${KRONN_REPOS_REL:-Repositories}:rw  # rw for agent writes
      # 0.8.3 — Self-mount Repositories at the SAME absolute host path inside
      # the container. Required for docker-in-docker workflows (Exec steps
      # that shell out to `docker compose run/exec` from a worktree). Without
      # this, the cwd seen by Kronn is `/host-home/Repositories/...`, but the
      # host docker daemon receives that same string as the volume source —
      # which doesn't exist on the host (only `${HOME}/Repositories/...` does).
      # Self-mounting makes the path resolve to the same bind on both sides,
      # so `docker compose` volume specs like `./:/app` mount the right files.
      # Mirrors the pattern at L36 for ~/.local/share (TD-20260513-docker-in-docker-volume).
      - ${KRONN_REPOS_DIR:-${HOME}/Repositories}:${KRONN_REPOS_DIR:-${HOME}/Repositories}:rw
      - ${KRONN_GLOBAL_BIN:-/usr/local/bin}:/host-bin/global:ro
      - ${KRONN_GLOBAL_LIB:-/usr/local/lib}:/host-bin/lib:ro  # npm global node_modules (sibling of /host-bin/global so `../lib/node_modules/...` relative symlinks resolve — fixes Fastly CLI which ships as a JS wrapper when installed via `npm -g`)
      - ${HOME}/.local/bin:/host-bin/local:ro
      - ${KRONN_NPM_BIN:-./.docker/empty}:/host-bin/npm:ro  # npm global bin (macOS: ~/.npm/bin or /opt/homebrew/bin)
      - /usr/bin:/host-bin/sysbin:ro              # Host system binaries (make, php, etc.)
      - /usr/sbin:/host-bin/syssbin:ro            # Host system admin binaries
      # 0.8.2 — Common version-manager paths so workflow Exec steps can
      # run `cargo` / `bun` / etc. without the user symlinking each tool
      # into ~/.local/bin. Each path goes through env-var indirection with
      # an EMPTY-DIRECTORY fallback (`./.docker/empty`) so Kronn STARTS even
      # when the user doesn't have rustup / bun installed.
      #
      # ⚠️ The fallback is a real empty directory, NOT `/dev/null`. On WSL2 +
      # Docker Desktop the daemon runs in a VM whose cross-VM layer cannot relay
      # the /dev/null character device as a bind SOURCE — runc creates /dev/null
      # as a directory in the container, `open(/dev/null)` fails with "is a
      # directory", and container init crashes (`make start`). An empty dir binds
      # cleanly everywhere and degrades gracefully (empty dir on PATH = no tool).
      #
      # To enable a real path, set the env var in your shell or a `.env` file:
      #   KRONN_CARGO_BIN=$HOME/.cargo/bin   (rustup default)
      #   KRONN_BUN_BIN=$HOME/.bun/bin
      #   KRONN_EXTRA_BIN=$HOME/.pyenv/shims (or any other path you need)
      - ${KRONN_CARGO_BIN:-./.docker/empty}:/host-bin/cargo:ro        # rustup Rust toolchain (cargo, rustc, clippy, fmt)
      # rustup's `cargo` and `rustc` are SHIMS — they need `~/.rustup` to find
      # the actual toolchain. Self-mount at the same absolute path so the shim's
      # hardcoded `$HOME/.rustup` lookup works inside the container. Set
      # KRONN_RUSTUP_HOME to enable. When UNSET, the empty dir is mounted at a
      # harmless absolute path (the rustup shim simply isn't usable — same as
      # cargo being absent above).
      - ${KRONN_RUSTUP_HOME:-./.docker/empty}:${KRONN_RUSTUP_HOME:-/host-bin/rustup-empty}:ro
      - ${KRONN_BUN_BIN:-./.docker/empty}:/host-bin/bun:ro            # Bun runtime + installer
      - ${KRONN_EXTRA_BIN:-./.docker/empty}:/host-bin/extra:ro        # Escape hatch — anything else (asdf shims, pyenv, custom paths)
      - /usr/libexec/docker/cli-plugins:/usr/libexec/docker/cli-plugins:ro  # Docker compose plugin
      # Read the machine's hostname mappings when project Docker endpoints are
      # inspected. `make start` snapshots the real host file because Docker
      # Desktop / OrbStack resolve a direct `/etc/hosts` bind inside their VM.
      - ${KRONN_HOSTS_SNAPSHOT:-./.docker/host-hosts-unavailable}:/host-etc/hosts:ro
      - ${HOME}/.local/share:/host-home/.local/share:ro  # Symlink targets (claude, vibe)
      # Self-mount so absolute-path symlinks created by host installers
      # (e.g. `~/.local/bin/claude` → `/home/<user>/.local/share/claude/versions/<v>/claude`)
      # resolve inside the container at the same absolute host path.
      # Without this, find_binary detects the binary via fallback but
      # exec fails on the broken symlink target
      # (TD-20260507-claude-symlink-not-resolvable).
      - ${HOME}/.local/share:${HOME}/.local/share:ro
      # 0.8.11 (B6) — uncomment + set KRONN_BACKUP_DIR=/backups to store scheduled
      # DB backups on the HOST, outside the data volume (so a lost volume doesn't
      # lose the backups too). Point the left side wherever you keep backups.
      # - ${KRONN_BACKUP_HOST_DIR:-./kronn-backups}:/backups:rw
      - npm-cache:/home/kronn/.npm  # Persistent npm cache for npx agent execution
      - uv-cache:/home/kronn/.cache/uv  # Isolated uv cache (named volume — see TD-20260429-uv-cache-uid-mismatch: bind-mounting ~/.cache/uv left root-owned files on host pre-APP_UID, breaking host `uvx` for MCP servers in CC CLI / Codex / Gemini)
      - uv-tools:/home/kronn/.local/share/uv  # Persistent uv tool installs (CloudWatch, Atlassian MCP servers)
      # SSH auth for git push (forward host SSH keys)
      - ${HOME}/.ssh:/home/kronn/.ssh:ro                    # SSH keys + known_hosts
      - ${SSH_AUTH_SOCK:-./.docker/empty}:/run/host-ssh-agent.sock  # SSH agent socket (Linux) — empty-dir fallback (see note above; /dev/null crashes WSL2+Docker Desktop). No agent → git push falls back to ~/.ssh keys.
      - ${KRONN_MACOS_SSH_SOCK:-./.docker/empty}:/run/host-services/ssh-auth.sock  # SSH agent socket (macOS Docker Desktop)
      # Agent auth directories (local subscriptions)
      # 0.7.1 — User-scoped agent context directory (cross-project, multi-CLI).
      # Markdown files here are auto-injected into the system prompt of every
      # agent Kronn spawns, regardless of CLI. Bridges the per-tool gap left
      # by ~/.claude/CLAUDE.md, ~/.codex/..., etc. Auto-bootstrapped with a
      # README on first read if the dir doesn't exist.
      - ${HOME}/.kronn/user-context:/home/kronn/.kronn/user-context:rw
      - ${HOME}/.claude.json:/home/kronn/.claude.json:ro    # Claude Code main config
      - ${HOME}/.claude:/home/kronn/.claude:rw              # Claude Code auth/config + session state (needs write)
      - claude-projects:/home/kronn/.claude/projects         # Isolate Docker sessions from host (avoids ghost /host-home project dirs)
      - claude-sessions:/home/kronn/.claude/sessions         # Isolate PID files (TD-20260507-claude-sessions-host-leak)
      - ${HOME}/.codex:/home/kronn/.codex:rw                # Codex auth + state (needs write)
      - ${HOME}/.config/codex:/home/kronn/.config/codex:ro  # Codex config (legacy path)
      - ${HOME}/.vibe:/home/kronn/.vibe:rw                  # Vibe auth + state (needs write)
      - ${HOME}/.kiro:/home/kronn/.kiro:rw                  # Kiro auth + state (AWS Builder ID)
      - ${HOME}/.copilot:/home/kronn/.copilot:rw              # GitHub Copilot auth + config
      - ${HOME}/.gemini:/home/kronn/.gemini:rw                # Gemini CLI auth + project registry + settings
      - ${HOME}/.local/share/kiro-cli:/home/kronn/.local/share/kiro-cli:rw  # Kiro SQLite data
      # RTK (Rust Token Killer) — share host's config + stats so `rtk gain`
      # inside the container reads the same SQLite the user's CLI writes to.
      # Without these, /api/rtk/savings always reports zero even when the
      # user has thousands of compressions on the host.
      - ${HOME}/.config/rtk:/home/kronn/.config/rtk:rw
      - ${HOME}/.local/share/rtk:/home/kronn/.local/share/rtk:rw
      - ./templates:/app/templates:ro                   # AI context templates for project init
      # Self-mount Kronn's scripts dir at the SAME absolute path inside
      # the container as on the host. Required so `kronn-internal` MCP
      # entries written into project-level config files (`.mcp.json`,
      # `.kiro/settings/mcp.json`, …) carry a path string that resolves
      # for BOTH the Kronn-spawned-in-container agent AND the user's
      # host CLI sessions (`kiro-cli`, `claude`, …). Without this the
      # host CLIs hit `Broken pipe (os error 32)` on every spawn.
      # Falls back gracefully when the source dir isn't present — the
      # injector skips kronn-internal rather than write a broken path.
      - ./backend/scripts:${PWD}/backend/scripts:ro
    environment:
      - KRONN_TEMPLATES_DIR=/app/templates
      - KRONN_DATA_DIR=/data
      - KRONN_RUN_ARTIFACTS_DIR=/run-artifacts
      - KRONN_IN_DOCKER=1
      - KRONN_HOST_HOME=${HOME}
      # Default zone of crons and date templates; a container reports UTC
      # without it. Settings > timezone overrides it.
      - TZ=${TZ:-}
      # Expose the security boundary to diagnostics. These values do not add
      # mounts; they mirror the primary + generated override mounts above.
      - KRONN_REPOS_DIR=${KRONN_REPOS_DIR:-${HOME}/Repositories}
      - KRONN_EXTRA_REPOS=${KRONN_EXTRA_REPOS:-}
      - KRONN_HOSTS_FILE=/host-etc/hosts
      # Path written into project-level `.mcp.json` / `.kiro/settings/…`
      # / `.gemini/settings.json` / `~/.codex/config.toml` for the
      # `kronn-internal` MCP entry. Must resolve identically inside the
      # container and on the host — the self-mount above makes that work.
      - KRONN_INTROSPECTION_PUBLIC_PATH=${PWD}/backend/scripts/disc-introspection-mcp.py
      - KRONN_HOST_BIN=/host-bin/local:/host-bin/global:/host-bin/npm:/host-bin/cargo:/host-bin/bun:/host-bin/extra
      # 0.8.2 — Propagate the user's rustup home so the `cargo` / `rustc`
      # shims can locate the active toolchain. The shims read this env
      # var BEFORE falling back to `$HOME/.rustup`, and the container's
      # `HOME` is `/home/kronn` (not the host user's home). Empty when
      # KRONN_RUSTUP_HOME isn't set in .env — cargo will then error out
      # cleanly at first invocation rather than silently fail.
      - RUSTUP_HOME=${KRONN_RUSTUP_HOME:-}
      - PATH=/home/kronn/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host-bin/local:/host-bin/global:/host-bin/npm:/host-bin/cargo:/host-bin/bun:/host-bin/extra:/host-bin/sysbin:/host-bin/syssbin
      - KRONN_HOST_OS=${KRONN_HOST_OS:-Linux}
      - KRONN_HOST_UID=${KRONN_HOST_UID:-1000}
      - KRONN_HOST_GID=${KRONN_HOST_GID:-1000}
      - KRONN_HOST_IPS=${KRONN_HOST_IPS:-}
      - SSH_AUTH_SOCK=/run/host-ssh-agent.sock
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
      - OPENAI_API_KEY=${OPENAI_API_KEY:-}
      - GEMINI_API_KEY=${GEMINI_API_KEY:-}
      - GOOGLE_API_KEY=${GOOGLE_API_KEY:-}
      - MISTRAL_API_KEY=${MISTRAL_API_KEY:-}
      - GITHUB_TOKEN=${GITHUB_TOKEN:-}
      - GH_TOKEN=${GH_TOKEN:-${GITHUB_TOKEN:-}}
      - OLLAMA_HOST=${OLLAMA_HOST:-http://host.docker.internal:11434}
      # 0.8.11 — LAN exposure is opt-in. The gateway/frontend host ports below
      # bind 127.0.0.1 by default; set KRONN_BIND=0.0.0.0 (e.g. for a WSL
      # backend reached from a Mac frontend) to publish on the LAN. When you do,
      # the backend REFUSES to start unauthenticated — set KRONN_AUTH_TOKEN, or
      # KRONN_ALLOW_INSECURE_LAN=1 to keep it open on a trusted home network.
      - KRONN_BIND=${KRONN_BIND:-127.0.0.1}
      - KRONN_ALLOW_INSECURE_LAN=${KRONN_ALLOW_INSECURE_LAN:-}
      - KRONN_AUTH_TOKEN=${KRONN_AUTH_TOKEN:-}
      # 0.8.11 (B6) — failure webhook (Slack/Teams/generic) + scheduled backups.
      # A backup dir OUTSIDE the data volume needs a host bind-mount (see the
      # commented volume below); set KRONN_BACKUP_DIR to /backups to use it.
      - KRONN_FAILURE_NOTIFY_URL=${KRONN_FAILURE_NOTIFY_URL:-}
      - KRONN_BACKUP_DIR=${KRONN_BACKUP_DIR:-}
      - KRONN_BACKUP_INTERVAL_HOURS=${KRONN_BACKUP_INTERVAL_HOURS:-24}
      - KRONN_BACKUP_KEEP=${KRONN_BACKUP_KEEP:-7}
      # RUST_LOG: if unset, the backend reads `config.server.debug_mode` to
      # pick a sensible default (info vs debug). Set `KRONN_RUST_LOG` in the
      # env (or via `./kronn start --debug` / `make start DEBUG=1`) to
      # override for one run without touching the persisted config.
      - RUST_LOG=${KRONN_RUST_LOG:-}
    # Resource limits — prevents runaway agents from consuming all host resources
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: '2.0'
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3140/api/health"]
      interval: 10s
      timeout: 3s
      # 60 s start_period covers the worst-case cold start on a fresh
      # machine: migrations + sidecar Python venv + tool installs +
      # ts-rs codegen. Inside `start_period` Docker is patient — failures
      # don't count toward `retries`. After the window, 5×10 s gives
      # another 50 s margin. Pre-fix this was 5 s × 3 retries → ~35 s
      # total, just below the real cold-start time, causing
      # `dependency failed to start` on every fresh `make start` (TD
      # TD-20260507-cold-start-healthcheck-race).
      retries: 5
      start_period: 60s

  frontend:
    restart: unless-stopped
    image: ghcr.io/docroms/kronn-frontend:${KRONN_IMAGE_TAG:-0.15.2}
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      # 0.8.11 — loopback by default; KRONN_BIND=0.0.0.0 opts into LAN exposure.
      - "${KRONN_BIND:-127.0.0.1}:3141:80"
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
    depends_on:
      backend:
        condition: service_healthy

  # Single entry point: nginx reverse proxy
  gateway:
    restart: unless-stopped
    image: nginx:alpine
    deploy:
      resources:
        limits:
          memory: 128M
          cpus: '0.25'
    ports:
      # 0.8.11 — main entry point. Loopback by default (secure out-of-the-box);
      # set KRONN_BIND=0.0.0.0 to expose on the LAN (backend then requires auth).
      - "${KRONN_BIND:-127.0.0.1}:3140:80"
    volumes:
      - ./.docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      backend:
        condition: service_healthy
      frontend:
        condition: service_healthy

volumes:
  kronn-data:
  kronn-run-artifacts:
  npm-cache:
  uv-cache:        # Isolated from host ~/.cache/uv to avoid uid-mismatch pollution (see TD-20260429)
  uv-tools:
  claude-projects:  # Isolates Docker Claude Code sessions from host ~/.claude/projects
  claude-sessions:  # Isolates `.claude/sessions/` PID files from the host daemon — without
                    # this, the container's Claude tries to talk to a host daemon at
                    # 127.0.0.1:<port> that doesn't exist there and hangs silently
                    # (TD-20260507-claude-sessions-host-leak).