Skip to main content

Overview

MeepaGateway can run each agent inside a Docker container, isolating the entire agent loop — LLM calls, tool execution, shell commands — from the host. A fresh container is spawned per message and destroyed after the response is sent. Container mode is disabled by default. Enable it for agents that run untrusted code, execute shell commands, or need a controlled environment with specific packages installed.

Isolation Models

MeepaGateway supports three isolation levels, from strongest to weakest:

Container

The agent’s full loop runs inside an isolated Docker container. The agent workspace is bind-mounted in; everything else on the host is inaccessible. File access rules do not apply — the container boundary is the enforcement mechanism. Enable with container_mode.enabled: true on the agent.

Seatbelt / Landlock

The agent runs on the host with kernel-level file access enforcement. This is the default mode when container mode is not enabled and file_access.unrestricted is not set. Rules are evaluated in priority order: deny always wins, then allow_read_write, then allow_read. Writes are blocked by default unless explicitly permitted.
  • macOS: enforced via sandbox-exec (seatbelt)
  • Linux: enforced via Landlock LSM

Unrestricted

No file access restrictions. Enable with file_access.unrestricted: true in config or the --unrestricted CLI flag.

How It Works

  1. A message arrives from a connector (MeepaChat, Discord, Slack)
  2. The host spawns a Docker container running meepagateway agent-run
  3. Message content, provider secrets, and config are passed via stdin JSON
  4. The agent workspace is bind-mounted at /workspace (read-write)
  5. The agent runs its full loop inside the container — including all tool calls
  6. The response is written to stdout as JSON
  7. The host reads the response, sends it through the connector, and the container exits
Only one container runs per agent at a time. A per-agent mutex prevents concurrent SQLite writes from overlapping container mounts.

Container Filesystem

  • HOME=/workspace — so ~/SOUL.md resolves to the agent’s persistent files
  • TMPDIR=/tmp — temp files go to the writable tmpfs
  • WORKDIR=/workspace
The bind mount maps to the host’s ~/.meepagateway/agents/{id}/ directory. Any file the agent writes to /workspace persists across container runs.

Container Image

The container image defines what packages are available inside the container. Configure it in the Captain Dashboard under Settings > Container Mode or in config.yaml:
When you click Build in the dashboard (or run meepagateway container build), MeepaGateway generates a Dockerfile from your package list, builds it, and tags the result as meepa-sandbox:<content-hash>. The tag changes automatically when you modify the package list.

Isolation Image

When container mode is enabled, MeepaGateway builds an isolation image that layers the meepagateway binary on top of the container image. This gives the container both:
  • All your configured packages (python3, nodejs, chromium, etc.)
  • The meepagateway agent-run binary that drives the agent loop
The isolation image is auto-built on first use. If a container image exists, it’s used as the base. Otherwise, the default ghcr.io/bogpad/meepagateway-sandbox:latest image is used. The image is tagged as meepagateway-isolated:{version}-{base} and cached locally. Changing container packages produces a new isolation image automatically.

Configuration

Container Mode (per-agent)

Enable container mode in config.yaml under each agent:

File Access (Seatbelt / Landlock mode)

When not using container mode, configure host-level file access restrictions:

Configuration Fields

boolean
default:"false"
Run this agent’s entire loop inside a Docker container.
string
default:""
Docker image for the container. Leave empty to auto-build from image_config (recommended).
integer
default:"300"
Maximum seconds before the container is killed.
string
default:"512m"
Docker memory limit (e.g. "512m", "1g").
string
default:"bridge"
Container network mode. "bridge" allows outbound connections (needed for LLM API calls from inside the container). "none" blocks all network access.
boolean
default:"false"
Disable all file access restrictions. Equivalent to the --unrestricted CLI flag.
string[]
default:"[]"
Paths the agent may read (but not write).
string[]
default:"[]"
Paths the agent may read and write.
string[]
default:"[]"
Paths always denied, regardless of other rules.
boolean
default:"false"
Allow read access to dotfiles in the home directory.
boolean
default:"true"
Allow outbound network access.

Security

Isolation containers run with hardened defaults:
  • Read-only rootfs — the image filesystem is immutable at runtime
  • All capabilities dropped--cap-drop=ALL
  • No privilege escalation--security-opt=no-new-privileges
  • Memory capped — containers exceeding memory_limit are OOM-killed
  • Ephemeral — each container is destroyed after the response
The only writable paths are the agent workspace bind mount (/workspace) and the /tmp tmpfs. Provider API keys are passed via stdin JSON and never written to disk.

Managing Images

Captain Dashboard

Go to Settings > Container Mode to:
  • Configure base image and packages
  • Preview the generated Dockerfile
  • Build, rebuild, or delete container images
  • Monitor build progress

CLI

Docker


Troubleshooting

”exec format error”

The isolation image contains a binary built for the wrong architecture. Delete the image and let it rebuild:

Packages not available in container

If the agent can’t find python3, nodejs, etc., the isolation image was built without the container base. Ensure the container image is built first, then delete the isolation image so it rebuilds:

SQLite disk I/O errors

Usually caused by a stale isolation image without proper HOME/TMPDIR settings. Delete and rebuild: