| name | terteform |
| description | Manages the Terteform Docker harness and local LLM servers. Use when asked to add domains to the whitelist, switch proxy modes, rebuild the image, manage terte containers, download/start/stop/health-check local LLMs, troubleshoot the Docker/OrbStack setup, or configure the mlx-lm or llama.cpp server. |
Terteform Harness
Terteform is a Docker-based security container that runs the pi.dev coding agent.
Its job is to constrain what pi can reach — on the filesystem and on the network.
See docs/security.md for the full threat model.
Roles
| Component | Role |
|---|
| Terteform | The Docker environment — the tin. Constrains filesystem and network access. |
| Pi.dev | The AI coding agent — the pie. Starts automatically when you run terte. |
| Squid proxy | Egress control — enforces the domain whitelist for all outbound traffic. |
| Local LLM | Runs on the Mac host, reachable via host.docker.internal. Not containerised. |
Key Paths
| What | Where |
|---|
| Harness repo | ~/terteform/ (your clone location) |
| Agent workspace | /workspace (bind-mounted from the project dir on Mac) |
| Persistent home | /home/piuser (named Docker volume — survives container runs) |
terte command | ~/bin/terte → terteform/docker/terte |
| Runtime config | docker/.runtime (gitignored) |
| Egress config | docker/squid/ |
| Security model | docs/security.md |
Usage
cd ~/code/my-project
terte
terte
Each project gets its own named home volume (terte_home_<dirname>) so pi's
downloaded tools (fd, rg) and config persist across runs. The container
itself is ephemeral (--rm) — only the volumes survive.
The terminal tab title is set to the project name via \033]0;name\a.
A 📂 project-name banner is printed before pi starts.
Runtime Configuration
Terteform auto-detects which container runtime is available (OrbStack, Docker
Desktop, Podman) and caches the result in docker/.runtime (gitignored).
# docker/.runtime
DOCKER=$HOME/.orbstack/bin/docker
DOCKER is the only variable. Compose commands are always $DOCKER compose.
| Action | Command |
|---|
| First-time detect (or re-detect) | make runtime-detect |
| Switch runtimes | Delete docker/.runtime, then make runtime-detect |
Repository Structure
terteform/
├── Makefile # root — run `make help` for all targets
├── README.md
├── docs/
│ └── security.md # threat model, IT security reference
├── docker/
│ ├── Dockerfile # node:20-slim + git + just + pi (pi is last layer)
│ ├── terte-init.sh # runs inside container: sets title, banner, starts pi
│ ├── terte # launcher: docker run --rm -it ... terte-init
│ ├── runtime-detect.sh # probes runtimes, writes .runtime
│ ├── Makefile # docker management targets
│ ├── .runtime # gitignored — active runtime config
│ ├── .env # secrets (not committed)
│ ├── .env.example # template
│ ├── models.json.example # template for pi local-provider config
│ └── squid/
│ ├── compose.yml # squid-only compose — network declared external
│ ├── squid.conf # enforce mode — whitelist only
│ ├── squid-audit.conf # audit mode — allow all, log all
│ └── whitelist.txt # allowed egress domains
Make Targets
make help
make setup
make build
make rebuild
make list
make stop-all
make proxy-audit
make proxy-enforce
make proxy-domains
make proxy-logs
Docker Image
Current size: ~800MB.
| Layer | Size | Notes |
|---|
| node:20-slim base | ~240MB | Debian-based |
| apt packages (git/curl/procps) | ~110MB | |
| terte-init.sh | ~1kB | session init script |
| pi npm install | ~290MB | upstream — last layer for cache efficiency |
Pi is the last layer — make build only re-runs the npm install when a new
version is available. Everything above stays cached.
make build
make build PI_VERSION=0.74.0
make rebuild
RAM per session: ~200–300MB
Container startup: < 1 second
No entrypoint — container runs as the host user directly, no root phase
Container Lifecycle
terte uses docker run --rm -it — containers are ephemeral by design.
- Workspace (
/workspace) — bind-mount from the Mac project dir
- Home volume (
terte_home_<dirname>) — persists pi tools and config across runs
- No state in the container itself — safe to remove anytime
The container name is terte_<dirname> (e.g. terte_myproject). Since --rm
is used, it disappears on exit. No stale container cleanup needed after rebuilds.
Network and Egress Proxy
The bridge network terteform_restricted_net is created by terte on first run.
squid/compose.yml declares it external: true — compose uses it without
trying to own it.
All outbound HTTP/HTTPS from containers routes through Squid.
host.docker.internal (local LLM access) bypasses the proxy.
Discovery workflow
make proxy-audit
terte
make proxy-domains
make proxy-enforce
Adding a domain
Edit docker/squid/whitelist.txt:
.example.com # wildcard — matches api.example.com etc.
exact.example.com # exact hostname only
Then: make proxy-enforce
Local LLM
Runs on the Mac host. Reachable from inside the container at:
http://host.docker.internal:<port>/v1
This bypasses Squid (host-gateway routing, stays on the Mac).
Pi discovers local models automatically via the local-llm Terteform plugin
(bundled in the image). At container startup plugin-init probes port 8001; if the
server is running it queries /v1/models live and injects the provider config into pi.
No config files need editing and no personal model names appear in any committed file.
If no local model is running the plugin is skipped; pi starts normally with
cloud providers only.
Port 8001 is reserved for local LLM use by convention.
See llm-manager/ and the llm-models skill for full lifecycle management.
Troubleshooting
Proxy warns about network labels
docker rm -f terte_egress_proxy
docker network rm terteform_restricted_net
terte
fd/rg downloading on every run
They install to the home volume. If they re-download every time, the volume
may have been removed. Check: docker volume ls | grep terte_home
Container name conflict (terte_ already exists)
A previous run didn't clean up (e.g. killed with SIGKILL). Remove it:
docker rm -f terte_<dirname>
terte
Pi not found / terte-init not found
Image predates terte-init. Rebuild:
make rebuild