Sidecars & SSH
Docker Compose setups for build sidecars and SSH host access — route tool commands to language-specific containers or remote hosts without heretic-cli
This page covers how to set up build sidecars and SSH host access using raw Docker Compose — without heretic-cli. If you use heretic-cli, see Sidecars (CLI) and SSH Host Access (CLI) instead.
Architecture Overview
The Heretic agent image ships with an entrypoint script that generates tool wrappers at startup. When the agent runs a command like npm install, the wrapper intercepts it and routes to the appropriate backend.
Tool Resolution Order
- Native — binary exists in the agent image → run directly
- Sidecar —
BUILD_SIDECARSenv var contains the command's runtime → route to sidecar - SSH —
SSH_HOSTis set → route via SSH to the remote host
Key Components
| Component | Path | Description |
|---|---|---|
| Entrypoint | /home/agent/entrypoint.sh | Generates wrappers at startup |
| Wrapper directory | /opt/sidecar/wrappers/ | Generated wrapper scripts |
| sidecar-exec | /opt/sidecar/sidecar-exec | Routes commands to sidecars |
| ssh-exec | /opt/sidecar/ssh-exec | Routes commands to remote host via SSH |
The BUILD_SIDECARS contract
sidecar-exec reads exactly one shape and only the internal_url field:
{"node":{"internal_url":"http://node-sidecar:8080"},
"python":{"internal_url":"http://python-sidecar:8080"}}
The older service-name form is gone
Early builds accepted {"node":"node-sidecar"} and shelled out to docker exec, which needed the Docker socket. The current client speaks HTTP to the exec-server only — a bare string value routes nothing. No socket, no privileged mode.
Sidecar Setup with the HTTP Exec-Server
Use builder images that include the exec-server binary (build them with heretic-cli image build-sidecar <runtime>). Communication is plain HTTP on a private compose network.
services:
agent:
image: heretic-agent:latest
container_name: claude-agent
stdin_open: true
tty: true
environment:
- AGENT_TYPE=claude
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- BUILD_SIDECARS={"node":{"internal_url":"http://node-sidecar:8080"},"python":{"internal_url":"http://python-sidecar:8080"}}
volumes:
- ./workspace:/workspace
working_dir: /workspace
depends_on:
- node-sidecar
- python-sidecar
node-sidecar:
image: builder-node:latest
volumes:
- ./workspace:/workspace
python-sidecar:
image: builder-python:latest
volumes:
- ./workspace:/workspace
Runtime Keys and Commands
| Runtime Key | Wrapped Commands | Builder Image (must speak the exec-server contract) |
|---|---|---|
node | npm, npx, pnpm, yarn, node | heretic-builder-node:latest |
python | python, python3, pip, pip3, poetry, pytest, ruff, black, mypy | heretic-builder-python:latest |
java | java, javac, mvn, gradle | heretic-builder-java:latest |
go | go, gofmt | heretic-builder-go:latest |
rust | cargo, rustc, rustfmt, clippy | heretic-builder-rust:latest |
Build them with heretic-cli image build-sidecar <runtime>, or ship your own image that serves the exec-server HTTP API. A plain runtime image such as node:22-alpine cannot be used as a sidecar any more — it has no exec-server.
All Five Runtimes (HTTP Mode)
services:
agent:
image: heretic-agent:latest
container_name: claude-agent
stdin_open: true
tty: true
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- BUILD_SIDECARS={"node":{"internal_url":"http://node-sc:8080"},"python":{"internal_url":"http://python-sc:8080"},"go":{"internal_url":"http://go-sc:8080"},"java":{"internal_url":"http://java-sc:8080"},"rust":{"internal_url":"http://rust-sc:8080"}}
volumes:
- ./workspace:/workspace
working_dir: /workspace
depends_on:
- node-sc
- python-sc
- go-sc
- java-sc
- rust-sc
node-sc:
image: builder-node:latest
volumes:
- ./workspace:/workspace
python-sc:
image: builder-python:latest
volumes:
- ./workspace:/workspace
go-sc:
image: builder-go:latest
volumes:
- ./workspace:/workspace
java-sc:
image: builder-java:latest
volumes:
- ./workspace:/workspace
rust-sc:
image: builder-rust:latest
volumes:
- ./workspace:/workspace
SSH Setup
Docker Compose with SSH
services:
agent:
image: heretic-agent:latest
container_name: claude-ssh
stdin_open: true
tty: true
environment:
- AGENT_TYPE=claude
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- SSH_HOST=${SSH_HOST}
- SSH_PORT=${SSH_PORT:-22}
- SSH_USER=${SSH_USER:-agent}
- SSH_HOST_CWD=${SSH_HOST_CWD:-/workspace}
volumes:
- ./workspace:/workspace
- ${SSH_KEY_FILE:-/dev/null}:/home/agent/.ssh/id_rsa:ro
working_dir: /workspace
Start with:
SSH_HOST=devbox.example.com SSH_KEY_FILE=~/.ssh/id_rsa docker compose up -d
SSH to Docker Host
Route commands to the machine running Docker:
services:
agent:
image: heretic-agent:latest
container_name: claude-host
stdin_open: true
tty: true
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- SSH_HOST=host.docker.internal
- SSH_PORT=22
- SSH_USER=${USER}
- SSH_HOST_CWD=${PWD}
volumes:
- ./workspace:/workspace
- ~/.ssh/heretic-agent:/home/agent/.ssh/id_rsa:ro
working_dir: /workspace
Linux Host
On Linux, host.docker.internal may not resolve. Use the Docker bridge IP (172.17.0.1) or add extra_hosts: ["host.docker.internal:host-gateway"] to the service.
Combined Setup
Use sidecars for fast runtimes and SSH for platform-specific tools:
services:
agent:
image: heretic-agent:latest
container_name: claude-combined
stdin_open: true
tty: true
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- BUILD_SIDECARS={"python":{"internal_url":"http://python-sidecar:8080"}}
- SSH_HOST=mac-mini.local
- SSH_USER=developer
- SSH_HOST_CWD=/Users/developer/projects/myapp
volumes:
- ./workspace:/workspace
- ~/.ssh/heretic-agent:/home/agent/.ssh/id_rsa:ro
working_dir: /workspace
depends_on:
- python-sidecar
python-sidecar:
image: heretic-builder-python:latest
volumes:
- ./workspace:/workspace
In this setup:
python3,pip,pytest→ routed to the Python sidecar (docker exec)swift,xcodebuild,go→ routed to the Mac mini via SSHgit,curl→ run natively in the agent container
Environment Variable Reference
Sidecar Variables
| Variable | Description | Default |
|---|---|---|
BUILD_SIDECARS | JSON mapping runtime names to sidecar config | — |
SIDECAR_TIMEOUT | Command timeout in seconds (HTTP mode) | 300 |
SIDECAR_CWD | Working directory override for sidecar commands | Current dir |
SIDECAR_STREAM | Set to 1 for SSE streaming output (HTTP mode) | 0 |
SSH Variables
| Variable | Description | Default |
|---|---|---|
SSH_HOST | Remote host address | — |
SSH_PORT | SSH port | 22 |
SSH_USER | SSH username | agent |
SSH_KEY_PATH | Path to private key inside container | /home/agent/.ssh/id_rsa |
SSH_HOST_CWD | Working directory on the remote host | Current dir |
Troubleshooting
Sidecar Commands Not Found
# Check BUILD_SIDECARS is set
docker exec claude-agent env | grep BUILD_SIDECARS
# List generated wrappers
docker exec claude-agent ls /opt/sidecar/wrappers/
# Check sidecar health (HTTP exec-server mode)
docker exec claude-agent curl http://node-sidecar:8080/health
# Check sidecar is running (docker exec mode)
docker ps | grep sidecar
# View sidecar logs
docker compose logs node-sidecar
SSH Backend Not Working
# Check SSH env vars
docker exec claude-ssh env | grep SSH_
# Test connectivity
docker exec claude-ssh ssh -o StrictHostKeyChecking=no \
-p ${SSH_PORT:-22} ${SSH_USER:-agent}@${SSH_HOST} echo "connected"
# Check key permissions
docker exec claude-ssh ls -la /home/agent/.ssh/id_rsa
# Must be -rw------- (600)
# Verbose SSH debug
docker exec claude-ssh ssh -vvv -o StrictHostKeyChecking=no \
${SSH_USER:-agent}@${SSH_HOST} echo "debug"
Wrappers Not Generated
# Check if entrypoint ran setup
docker exec claude-agent ls /opt/sidecar/wrappers/
# If empty, check that BUILD_SIDECARS or SSH_HOST is set
docker exec claude-agent env | grep -E "BUILD_SIDECARS|SSH_HOST"
Sidecar Timeout (HTTP Mode)
# Increase timeout (default 300s)
# Add to environment:
SIDECAR_TIMEOUT=600
# Or per-command:
docker exec claude-agent bash -c 'SIDECAR_TIMEOUT=600 npm run build'
Next Steps
- Running Containers — Basic Docker Compose setups
- Building Images — Build agent and sidecar images
- Customizing Images — Add tools and runtimes to images
- Sidecars (CLI) — Sidecar setup via heretic-cli
- SSH Host Access (CLI) — SSH setup via heretic-cli