Skip to content

D8: Compose example + docs surface #13

Description

@Teagan42

Part of #2

D8 — Compose example + docs surface

Labels: wayfinder:grilling
Labels: wayfinder:grilling status:closed
Blocked by: D1 (closed), D2 (closed), R2 (closed), D3 (closed), D4 (closed), D5 (closed), D9 (closed)

Resolution

Docs layout — both surfaces:

  • docs/getting-started.md — short new section "Run chartr as a Docker container". ~15 lines: the compose one-liner path, a pointer to docs/docker.md for depth.
  • docs/docker.md — new page, deep dive. Table of contents:
    1. What you get (single-user self-hosted, ports, baked agents from D9)
    2. Quick start (compose + docker run alternative)
    3. Configuration reference (env vars, volumes, PUID/PGID)
    4. Agent authentication (two patterns)
    5. Adding your own agents (/opt/agents bind-mount from D5)
    6. Remote access (Caddy + Traefik reverse-proxy examples, requires CHARTR_PUBLIC_URL from R2)
    7. Troubleshooting
    8. Verifying the image (cosign snippet from D7)
  • README.md — one-line addition to the install section pointing at docs/docker.md.

Canonical compose file — ship a real packaging/docker/compose.yaml in-repo AND embed it in docs/docker.md. Sync check: a small make or CI step diffs the doc-embedded block against the file (acceptable modest drift risk if the sync step is skipped for v1).

packaging/docker/compose.yaml (spec — not committed by this map):

services:
  chartr:
    image: ghcr.io/rengwu/chartr:latest    # or :edge for main
    container_name: chartr
    ports:
      - "127.0.0.1:8787:8787"              # localhost-only per destination Notes
    environment:
      PUID: "${UID:-1000}"                  # D4: LSIO uid model
      PGID: "${GID:-1000}"
      CHARTR_PUBLIC_URL: "http://localhost:8787"   # R2: required if you remap the port or reverse-proxy
      # ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY: use `env_file` below
    env_file:
      - .env                                # holds API keys per D5 auth pattern (b)
    volumes:
      - chartr-data:/data                   # D3: runtime
      - chartr-config:/config               # D3: operator config
      # Bind-mount your source repos wherever your agents want to see them:
      - ${HOME}/src:/workspace:rw           # D5: repo trees come from operator, not chartr's volumes
      # D5 auth pattern (a): mount agent state as needed. Example for Claude Code:
      # - ${HOME}/.claude:/config/agents/.claude:rw
      # D5 extension point: your own agent binaries shadow the baked set.
      # - ${HOME}/.local/bin:/opt/agents:ro
    restart: unless-stopped

volumes:
  chartr-data:
  chartr-config:

Compose-averse docker run (in docs alongside the compose block):

docker run -d --name chartr \
  -p 127.0.0.1:8787:8787 \
  -e PUID=$(id -u) -e PGID=$(id -g) \
  -e CHARTR_PUBLIC_URL=http://localhost:8787 \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  -v chartr-data:/data \
  -v chartr-config:/config \
  -v "$HOME/src:/workspace:rw" \
  --restart unless-stopped \
  ghcr.io/rengwu/chartr:latest

Reverse-proxy snippets (in docs/docker.md §6). Both assume CHARTR_PUBLIC_URL=https://chartr.example.com in compose.

  • Caddy (Caddyfile):

    chartr.example.com {
        reverse_proxy 127.0.0.1:8787
    }

    Caddy handles auto-TLS, HTTP/2, and websocket upgrades automatically — no extra config needed for /ws/control and /ws/terminal/{id}.

  • Traefik (compose labels on the chartr service — alternative to the standalone reverse proxy):

    labels:
      - traefik.enable=true
      - traefik.http.routers.chartr.rule=Host(`chartr.example.com`)
      - traefik.http.routers.chartr.entrypoints=websecure
      - traefik.http.routers.chartr.tls.certresolver=letsencrypt
      - traefik.http.services.chartr.loadbalancer.server.port=8787

    Traefik forwards websockets by default when the target is an http:// service; no Upgrade/Connection header override needed. Server-side ping frames (D2, 30s) handle any idle-timeout defaults.

Troubleshooting section entries (each is one paragraph in docs/docker.md §7):

Symptom Cause Fix
Blank page / connection refused at http://localhost:9000 CHARTR_PUBLIC_URL not set for remapped host port (R2) Set CHARTR_PUBLIC_URL=http://localhost:9000 and restart
Blank page over reverse-proxied HTTPS domain CHARTR_PUBLIC_URL still http://localhost:8787 (R2) Set to the full https://chartr.example.com
Files in bind-mounted repo owned by wrong uid on host user: set in compose (bypassed root entrypoint, D4) Remove user:; use PUID/PGID env instead
"Not on your PATH" for an agent CLI Agent isn't baked and no /opt/agents mount (D5, D9) Bind-mount your binaries dir at /opt/agents, or install into /config/… and add to PATH via compose
Container restarts every 30s HEALTHCHECK failing — chartr can't serve /api/health (D6) docker logs chartr — often a broken bind-mount preventing startup
Websocket disconnects after 60s through a proxy Proxy has aggressive idle timeout Server sends 30s pings (D2); if still dropping, raise proxy read_timeout to ≥60s
Agent asks to log in every time Agent state dir not bind-mounted (D5) Add e.g. -v $HOME/.claude:/config/agents/.claude:rw

Verify-the-image snippet (§8, from D7):

cosign verify \
  --certificate-identity-regexp "https://github.com/rengwu/chartr/.github/workflows/docker-publish.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/rengwu/chartr:latest

Downstream impact: none — this was the last ticket. Map is complete: the way to the destination is clear.

Not-yet-specified section on the map — clear reverse-proxy guidance (folded into this ticket) and health endpoint surface (D1 committed to reusing existing /api/health; nothing else to specify).

Question

Spec what lands in docs/ for the getting-started-with-docker path:

  • Location: new docs/docker.md? Section in existing docs/getting-started.md? Both?
  • A compose.yaml example that pulls together every prior decision: image ref (ghcr.io/…:latest), port bind to 127.0.0.1:PORT, volumes for state (D3), uid/gid handling (D4), bind mounts for repos and agent CLIs (D5), env vars for the serve command (D1).
  • A minimal docker run one-liner equivalent for the compose-averse.
  • A "what next" pointer for remote access (reverse proxy — deferred to Not-yet-specified on the map).
  • Troubleshooting stubs (permission-denied on bind mounts → D4; agent CLI not found → D5; port already in use → D1).

Deliverable: a docs outline + the exact compose.yaml the spec commits to, ready to be dropped in by the implementation effort.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions