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 |
Sidecar Execution Modes
There are two sidecar execution modes depending on the BUILD_SIDECARS format:
| Mode | BUILD_SIDECARS Format | How it works |
|---|---|---|
| docker exec | {"node":"node-sidecar"} (service name) | sidecar-exec finds the container and runs docker exec |
| HTTP exec-server | {"node":{"internal_url":"http://node-sidecar:8080"}} | sidecar-exec sends HTTP POST to the exec-server |
The docker exec mode requires Docker socket access. The HTTP exec-server mode requires sidecar images with the exec-server binary (e.g., builder-node:latest).
Sidecar Setup with docker exec
Use standard runtime images (e.g., node:22-alpine). The agent container uses docker exec to run commands inside the sidecar. Requires Docker socket access.
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":"node-sidecar","python":"python-sidecar"}
volumes:
- ./workspace:/workspace
- /var/run/docker.sock:/var/run/docker.sock
working_dir: /workspace
node-sidecar:
image: node:22-alpine
volumes:
- ./workspace:/workspace
command: ["/bin/sh", "-c", "sleep infinity"]
python-sidecar:
image: python:3.12-slim
volumes:
- ./workspace:/workspace
command: ["/bin/sh", "-c", "sleep infinity"]
Docker Socket
Mounting the Docker socket gives the container access to the host Docker daemon. The agent needs this to run docker exec against sidecar containers.
Sidecar Setup with HTTP Exec-Server
Use builder images that include the exec-server binary (e.g., builder-node:latest). No Docker socket needed — communication is over HTTP.
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 | Standard Image |
|---|---|---|---|
node | npm, npx, pnpm, yarn, node | builder-node:latest | node:22-alpine |
python | python, python3, pip, pip3, poetry, pytest, ruff, black, mypy | builder-python:latest | python:3.12-slim |
java | java, javac, mvn, gradle | builder-java:latest | eclipse-temurin:21 |
go | go, gofmt | builder-go:latest | golang:1.23-alpine |
rust | cargo, rustc, rustfmt, clippy | builder-rust:latest | rust:1.75 |
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":"python-sidecar"}
- SSH_HOST=mac-mini.local
- SSH_USER=developer
- SSH_HOST_CWD=/Users/developer/projects/myapp
volumes:
- ./workspace:/workspace
- /var/run/docker.sock:/var/run/docker.sock
- ~/.ssh/heretic-agent:/home/agent/.ssh/id_rsa:ro
working_dir: /workspace
depends_on:
- python-sidecar
python-sidecar:
image: python:3.12-slim
volumes:
- ./workspace:/workspace
command: ["/bin/sh", "-c", "sleep infinity"]
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