# The Loopgate runner and its Slack worker, from the published image; README.md says every step.
name: loopgate-runner
x-image: &image
  image: ghcr.io/vigneshgce/loopgate-runner:aa55eed@sha256:0236203514f3c95885512af12d4dfc83d4833e425d35be25c9fb8dfa4a2a76d2
services:
  runner:
    <<: *image
    restart: unless-stopped
    init: true
    env_file: runner.env
    # Workspaces cloned or started from the page live on the repos volume. Its address is the
    # quick tunnel's until LOOPGATE_RUNNER_URL in runner.env names a permanent one (ADR 0137).
    environment:
      LOOPGATE_REPOS_DIR: /repos
      LOOPGATE_ADDRESS_FROM: http://address:2000/quicktunnel
    ports:
      # For a proxy on the box (Tailscale Serve or your own), passing Host through.
      - 127.0.0.1:4311:4311
    healthcheck:
      test:
        [
          CMD,
          node,
          -e,
          "fetch('http://127.0.0.1:4311/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
        ]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    volumes:
      - data:/data
      - repos:/repos
      - home:/home/node
  # The Slack worker (ADR 0135): always on, reading Settings → Slack from the daemon's socket on the
  # shared data volume; no ports.
  slack:
    <<: *image
    working_dir: /app/apps/cli
    command: [node, --import, tsx, /app/apps/slack/src/index.ts]
    restart: unless-stopped
    init: true
    # Idle until Settings → Slack is connected; a slack.env with SLACK_APP_TOKEN overrides the page.
    env_file: [{ path: slack.env, required: false }]
    environment: { LOOPGATE_REPOS_DIR: /repos }
    depends_on:
      runner:
        condition: service_healthy
    volumes:
      - data:/data
  # The runner's first address: a Cloudflare quick tunnel, no account or token, a new
  # …trycloudflare.com hostname at each restart; the runner reads it from the metrics port.
  address:
    image: cloudflare/cloudflared:2026.9.3
    command: [tunnel, --no-autoupdate, --metrics, 0.0.0.0:2000, --url, http://runner:4311]
    restart: always
    depends_on: [runner]
  # Optional: a permanent address, a named Cloudflare Tunnel (guides/address-cloudflare.md). Its
  # public hostname points at http://runner:4311 on this network; the box opens no inbound port.
  tunnel:
    image: cloudflare/cloudflared:2026.9.3
    command: [tunnel, --no-autoupdate, run]
    profiles: [tunnel]
    restart: unless-stopped
    env_file: tunnel.env
    depends_on: [runner]
volumes:
  data:
  repos:
  home:
