- 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
```bash
cd ~/code/my-project
terte # fresh container starts, pi launches automatically
# tab/window title set to project name
# on exit → back to Mac, container removed, volumes preserved
terte # next run: same volumes, pi tools already installed
```
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
```bash
make help # full target list
make setup # first-time: build image + symlink terte onto ~/bin
make build # update image (new pi version etc.) — cached, only pi layer reruns
make rebuild # full no-cache rebuild
make list # list all terte containers and home volumes
make stop-all # stop all running terte containers
make proxy-audit # egress discovery mode — allow all, log everything
make proxy-enforce # production mode — whitelist only
make proxy-domains # print domains seen in audit logs
make proxy-logs # tail live 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.
```bash
make build # queries npm for latest pi version
make build PI_VERSION=0.74.0 # pin a specific version
make rebuild # full no-cache 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
```bash
make proxy-audit # switch to audit mode
terte # run pi, exercise all tools and workflows
make proxy-domains # see what was contacted
# edit docker/squid/whitelist.txt
make proxy-enforce # lock back down
```
### 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**
```bash
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_<name> already exists)**
A previous run didn't clean up (e.g. killed with SIGKILL). Remove it:
```bash
docker rm -f terte_<dirname>
terte
```
**Pi not found / terte-init not found**
Image predates terte-init. Rebuild:
```bash
make rebuild
```
GitHub에서 보기