# Isolated AI coding agent on Linux design template (Podman, overlayfs, bindfs, git)

> Source: <https://gist.github.com/amartinr/5866cb0a6374ac217cefef3af1780f09>
> Published: 2026-08-29 09:42:12+00:00

Reference architecture for running a coding agent in an isolated container with controlled read/write access to a host home directory, versioned via git, and routed through a local gateway to remote models.

Design template. Adapt values, paths, and tool choices to the target environment.

- A Linux distribution with systemd and standard Unix permission semantics (Debian 13 used as reference).
- Podman, rootless, no daemon.
- overlayfs and FUSE/bindfs available.
- `fuse-overlayfs` and`bindfs` installed.
- The `fuse` package (providing`fusermount` ) installed, required for FUSE
mounts via`/etc/fstab` .
- yadm (or an equivalent dotfiles manager) for the host home repo.
- A local LLM gateway reachable over a VPN interface.
- `etckeeper` available for system configuration versioning.

```
Pi agent (Podman container, --network=host, host user: pi-agent)
   |  network -> Bifrost (wg0 VPN) -> DeepSeek
   v  writes into /work
/srv/pi/work  (overlayfs merged, the only directory mounted into the container)
   |- overlayfs:
   |    lowerdir = host home (read-only)
   |    upperdir = /srv/pi/.overlay/upper (writable, agent changes)
   |    workdir  = /srv/pi/.overlay/work (kernel scratch)
   |- git repo: versioned agent changes
   |- controlled promotion (test branch -> merge)
   v
Host home (yadm, umask 007, .gitignore excludes secrets and Work)
   ^
   bindfs (fstab, root) -> ~/Work (host user transparent access)

/etc (system configuration, versioned with etckeeper)
   ^  human applies agent-proposed changes with sudo
```

Properties:

- The agent writes only into `/srv/pi/work` (the overlay merged layer).
- The host home is the overlay lowerdir, read-only. The agent reads it without duplicating data and cannot write to it.
- The agent runs as a separate user/group (`pi-agent` ), so Unix permissions
protect host files.
- Only `work` is mounted into the container. The agent cannot see`.overlay` ,
the host home, or any host paths.
- Changes are versioned in git (work). The overlay layer isolates the agent's writes from the host home.
- Promotion to the real home is human-controlled.
- System configuration (`/etc` ) is never written by the agent directly; the
agent proposes, the human applies with sudo, and`etckeeper` records it.
- All mounts (overlay and bindfs) are performed by root via `/etc/fstab` .
The host user lacks ownership of`/srv/pi` , and`bindfs` UID remapping
requires root; therefore no user mount units are used.

| Item | Configuration | 
|---|---|
| Agent | Pi (Mario Zechner terminal harness) | 
| Runtime | Podman, rootless, no daemon | 
| Network | `--network=host` | 
| Host user | `pi-agent` , group`pi-agent` , system user | 

Inside the container:

| Path | Role | 
|---|---|
| `/home/pi` | Agent home (config, caches) - ephemeral | 
| `/work` | Work directory, mounted to `/srv/pi/work` | 

Only `/work` is mounted into the container. `/home/pi` is ephemeral; the agent
does not retain config or caches between sessions. The agent has no access to
`.overlay`, the host home, or host filesystem paths.

Launch (overlay mode):

```
podman run --rm -it --network=host \
  --env HOME=/home/pi \
  --volume /srv/pi/work:/work:Z \
  imagen-pi
```

**System user (UID < 1000) decision:**

The agent is a system user (`pi-agent`): it runs as a service rather than an
interactive account, and its dedicated group reinforces permission separation
from the host user. Adjustments required:

- Assign a usable login shell (system users default to `nologin` /`false` ).
- Add explicit user-namespace ranges for rootless Podman in `/etc/subuid` and`/etc/subgid` :

```
pi-agent:100000:65536
```

Launch as the agent user with the runtime dir set:

```
sudo -u pi-agent XDG_RUNTIME_DIR=/run/user/$(id -u pi-agent) podman ...
```

**Critical requirement - host home protection:**

The agent MUST run as the `pi-agent` user, never as the host user's UID.
Running the container with the host user's UID invalidates permission
protection: the agent becomes the owner of host files and umask 007 no longer
applies. The overlay lowerdir read-only mount prevents writes but not reads if
the agent shares the host UID.

**Containerfile (from official Pi plain-Docker pattern):**

```
FROM node:24-bookworm-slim
RUN apt-get update \
 && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
 && rm -rf /var/lib/apt/lists/*
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
RUN useradd -m -s /bin/bash -d /home/pi pi && chown -R pi:pi /home/pi
WORKDIR /work
ENV HOME=/home/pi
COPY pi-config/ /home/pi/.pi/agent/
USER pi
ENTRYPOINT ["pi"]
```

The `pi-config/` directory holds the agent configuration (Bifrost endpoint as
an OpenAI-compatible provider). Credentials are injected at runtime, not
committed to the image.

| Item | Configuration | 
|---|---|
| Gateway | Bifrost (local) | 
| Access | Reachable via `wg0` VPN interface | 
| Models | DeepSeek | 
| Agent link | OpenAI-compatible endpoint | 
| Network | `--network=host` ; proxy available as an outbound control point | 

The upstream model credential lives only in the gateway. The agent talks to the gateway and never sees the upstream key.

| Item | Configuration | 
|---|---|
| Work (merged) | `/srv/pi/work` | 
| overlayfs lowerdir | Host home, read-only | 
| overlayfs upperdir | `/srv/pi/.overlay/upper` , owned by`pi-agent` | 
| overlayfs workdir | `/srv/pi/.overlay/work` | 
| Host access | `bindfs` ->`~/Work` (transparent) | 

`upperdir` and `workdir` must not reside inside the `lowerdir`. Placing them
under `/srv/pi/.overlay` (outside the host home) avoids overlay recursion.

**Transient overlay - per-session lifecycle:**

The overlay layer is disposable and recreated for each session to avoid divergence between the host home (lowerdir) and stale upperdir data.

- Session start: discard the previous upperdir, mount a fresh overlay against the current host home.
- During the session: the agent reads the host home and writes to the fresh upperdir.
- Session end: promote approved changes to the host home via git, then discard the upperdir.

Do not edit host-home files that the agent is concurrently modifying; overlayfs gives priority to the upperdir, hiding the host edit from the agent. Git resolves any conflict during promotion. Discarding the upperdir deletes uncommitted agent work; promote before discarding.

**Safe reset procedure:**

Before remounting for a new session, discard the upperdir without risking the host home. Use a script that rejects unsafe paths and deletes only content:

``` bash
#!/bin/bash
set -euo pipefail

UPPERDIR="/srv/pi/.overlay/upper"
WORKDIR="/srv/pi/.overlay/work"

# Safety check: reject unsafe paths
for d in "$UPPERDIR" "$WORKDIR"; do
  if [ "$d" = "$HOME" ] || [ "$d" = "/" ] || [ -z "$d" ]; then
    echo "Refusing to discard: unsafe path ($d)" >&2
    exit 1
  fi
done

# Verify the agent is stopped
if pgrep -u pi-agent -f "podman" >/dev/null 2>&1; then
  echo "Agent still running; stop it before discarding" >&2
  exit 1
fi

# Delete only content, never the root directory
find "$UPPERDIR" -mindepth 1 -delete
find "$WORKDIR" -mindepth 1 -delete

echo "Overlay layer discarded safely."
```

The reset flow is: stop the agent, promote approved changes, unmount the overlay, run the reset script, remount, relaunch.

The same image supports two modes of use. The only difference is what is
mounted at `/work`.

**Mode 1 - Overlay (home-aware):** the agent sees the host home read-only via
the overlay and writes into `/srv/pi/work`.

```
podman run --rm -it --network=host \
  --env HOME=/home/pi \
  --volume /srv/pi/work:/work:Z \
  imagen-pi
```

**Mode 2 - Project (repository):** the agent is limited to a single host
directory (a git repository) mounted at `/work`. The host home and its secrets
are entirely outside the container. This provides maximum isolation and is
simpler than the overlay, since no overlayfs is involved. Reversion is handled
by git in the repository.

```
podman run --rm -it --network=host \
  --env HOME=/home/pi \
  --volume /home/TU_USUARIO/proyectos/mi-repo:/work:Z \
  imagen-pi
```

Use Mode 2 for concrete tasks on individual repositories; use Mode 1 when the
agent must read the host home/global config while writing only into `work`.

Both mounts are performed by root via `/etc/fstab`. The host user does not own
`/srv/pi`, and `bindfs` UID remapping requires root, so no user mount units are
used.

```
# 1. Overlay (fuse-overlayfs)
/srv/pi/.overlay/upper /srv/pi/work fuse.overlayfs lowerdir=/home/TU_USUARIO,upperdir=/srv/pi/.overlay/upper,workdir=/srv/pi/.overlay/work,x-systemd.automount 0 0

# 2. Bindfs (options comma-separated; map without leading --)
/srv/pi/work /home/TU_USUARIO/Work fuse.bindfs map=TU_USUARIO/pi-agent,x-systemd.requires=/srv/pi/work,x-systemd.after=/srv/pi/work 0 0
```

Requirements:

- The `fuse` package (providing`fusermount` ) must be installed for FUSE
mounts via fstab.
- In fstab, options are comma-separated. The bindfs mapping option is `map=` ,
not`--map=` .
- If root mounts the bindfs and the host user accesses `~/Work` , set`user_allow_other` in`/etc/fuse.conf` .
- Mount order: overlay first (`/srv/pi/work` ), bindfs second (`~/Work` ),
enforced with`x-systemd.requires` /`x-systemd.after` .

The agent must never write directly to `/etc`. System configuration is handled
through a propose-and-apply flow with `etckeeper`:

```
1. The agent proposes a /etc change as a draft in work.
2. The human reviews the draft.
3. The human applies the change to /etc with sudo.
4. etckeeper records the change (automatic commit with diff).
5. If a change breaks the system, revert with git in /etc.
```

`etckeeper` versions `/etc` with git and hooks into the package manager
(`apt`/` dpkg` on Debian), committing the state before and after package
operations.

Setup (Debian):

```
sudo apt install etckeeper
sudo etckeeper init
```

Notes:

- `etckeeper init` creates a baseline commit of the current`/etc` without
deleting anything; existing configuration becomes the starting point.
- `etckeeper` excludes sensitive files by default (`/etc/shadow` ,`/etc/gshadow` , private keys). Verify exclusions on setup.
- The `/etc` git repository may be exposed through the overlay if the host home
is the lowerdir; protect or relocate it.

| Scope | Tool | Notes | 
|---|---|---|
| Host home | yadm | Git-based dotfiles management | 
| Work ( `/srv/pi/work` ) | git | Separate repo for agent changes | 
| `/etc` | etckeeper | System configuration, hook-driven | 
| yadm `.gitignore` | - | Exclude `Work` and all secrets | 

The yadm repository lives inside the host home and is exposed through the overlay. With umask 007 its files are group-only; relocate it outside the home for additional isolation.

```
1. Agent commits changes in work.
2. Reviewer inspects the diff.
3. Promote to a test branch on the home repo.
4. Validate on the branch.
5. Merge to the default branch, or apply a selective rsync.
```

The human approves every promotion. Nothing moves from work to the home automatically. Promote before discarding the overlay layer.

| Item | Configuration | 
|---|---|
| umask | `007` (user scope): new files/dirs 660/770 | 
| Agent user | Different group ( `pi-agent` ), falls under "others" | 
| `.ssh` | Restricted (600/700) | 
| overlayfs | Respects permissions | 

umask 007 protects against the `pi-agent` user only while that user is not a
member of a group with read access. It is not retroactive; existing files with
`others` read bits require a one-time hardening pass.

Host home protection is a primary requirement. All layers depend on the agent running as a separate user/group. Do not launch the agent with the host user's UID.

- Secrets are never committed to git (yadm, work, or /etc via etckeeper).
- Command allowlist/denylist restricts the agent.
- Host network is accepted; a proxy can act as an optional outbound allowlist.
- Agent logs are reviewed to detect prompt injection or anomalous behavior.

| Layer | Purpose | 
|---|---|
| overlayfs | Discard the layer for a per-session reset | 
| git (work) | Fine-grained revert of changes | 
| etckeeper | Revert of `/etc` changes | 
| restic / rsnapshot | Cover binaries, caches, non-text data | 
| External | Off-machine copies; same-disk snapshots insufficient | 

- Containerfile for the agent, versioned in yadm.
- fstab entries for the overlay and the bindfs.
- `etckeeper` config/state for`/etc` .

| Decision | Choice | Considered alternatives | 
|---|---|---|
| Workspace isolation | overlayfs | btrfs snapshots, container volume, plain dir | 
| Home exposure | bindfs --map | symlink, `mount --bind` | 
| Default umask | 007 | 077 (owner-only), 022 (group-read) | 
| Dotfiles manager | yadm | chezmoi, GNU Stow, plain git | 
| Container runtime | Podman | Docker, systemd-nspawn | 
| Sandboxing | container | bubblewrap, firejail, Landlock | 
| Agent account type | system user | regular user (UID >= 1000) | 
| Mount location | fstab (root) | user mount units | 
| System config | etckeeper | NixOS declarative config | 

Rationale:

- overlayfs exposes the home read-only and stores only deltas.
- bindfs --map resolves UID mismatch while keeping underlying files owned by
`pi-agent` .
- umask 007 permits group sharing while excluding "others"; 077 is stricter.
- Podman is rootless and daemon-less, matching a minimal setup.
- A system user models the agent as a service; the tradeoff is subuid/subgid setup.
- Both mounts live in fstab: the host user does not own `/srv/pi` , and bindfs
UID remapping requires root. User mount units cannot remap owners, so fstab
(root) is required.
- Mode 2 (project/repository) provides maximum isolation with no overlayfs and git-only reversion; Mode 1 (overlay) provides home-aware context when needed.
- `etckeeper` versions`/etc` declaratively and integrates with the package
manager, keeping the agent out of system paths.

- Overlay recursion: upperdir/workdir must not reside inside lowerdir.
- Whiteouts: an agent can create deletion markers in the upperdir, hiding files in its view without modifying the host home.
- UID mismatch: a bindfs --map that remaps host files to the agent user's UID negates permission protection.
- Exposed yadm repo: its git history is readable through the overlay if not protected or relocated.
- Non-filesystem vectors: /proc, /sys, environment, network are not covered by file permissions.
- umask is not retroactive: existing permissive files require a separate pass.
- Rootless Podman as a system user (UID < 1000) requires explicit subuid/subgid ranges.
- Launching the container with the host user's UID invalidates permission protection on the host home.
- A system user defaulting to a non-login shell must be assigned a usable shell.
- Layer divergence: editing a host-home file the agent modified in the upperdir hides the host edit from the agent (upperdir takes priority). Discard and remount per session.
- The `fuse` package must be installed for FUSE mounts via fstab; otherwise the
mount fails at boot even if the CLI mount works.
- In fstab, bindfs options are comma-separated and use `map=` (no leading`--` ).
- If root mounts the bindfs and the host user accesses it, `user_allow_other` must be set in`/etc/fuse.conf` .
- Mode 2 mounts a host directory directly; ensure it contains no secrets and that only the intended repository is exposed.
- The `etckeeper` repository lives inside`/etc` and may hold sensitive
configuration history; verify exclusions and protect access.

1. Create the `pi-agent` user/group; assign a home outside the host home.
2. Assign a usable login shell to `pi-agent` .
3. Add subuid/subgid ranges for `pi-agent` in`/etc/subuid` and`/etc/subgid` .
4. Install the agent in the container; wire it to the gateway.
5. Set `umask 007` in the shell profile (user scope).
6. Harden existing files with `others` read bits (one-time pass).
7. Create `/srv/pi/work` ,`/srv/pi/.overlay/upper` ,`/srv/pi/.overlay/work` .
8. Initialize the git repo in `/srv/pi/work` .
9. Install `fuse` ,`fuse-overlayfs` ,`bindfs` ; set`user_allow_other` in`/etc/fuse.conf` if required.
10. Add the overlay and bindfs entries to `/etc/fstab` ; run`mount -a` .
11. Exclude `Work` and secrets from the dotfiles repo.
12. Define the agent command allowlist/denylist.
13. Configure backups and external copies.
14. Test the full flow: agent edits in work, diff, branch, merge.
15. Always launch the agent as `pi-agent` , never the host user's UID.
16. Discard and remount the overlay at each session start; promote before discarding.
17. Install and initialize `etckeeper` ; verify its sensitive-file exclusions.
18. Apply agent-proposed `/etc` changes manually with sudo; let etckeeper record
them.
