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

PathTypeWritablePersistsPurpose
/workspaceBind mountYesYesAgent files (SOUL.md, MEMORY.md, memory.db, skills/)
/tmptmpfs (256MB)YesNoTemp files (SQLite journals, downloads)
Everything elseImage rootfsNoN/ARead-only (OS, packages, meepagateway binary)
  • 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:
image_config:
  enabled: false
  image: ''
  base_image: ghcr.io/bogpad/meepagateway-sandbox:latest
  packages: []
  env_vars: {}
  inject_credentials_env: true
  inject_secrets_env: true
  redact_secrets: true
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:
agents:
  - id: my-agent
    container_mode:
      enabled: true
      image: ''             # Empty = auto-build from image_config
      memory_limit: 512m
      timeout_seconds: 300
      network: bridge       # "bridge" (allows outbound) or "none" (no network)

File Access (Seatbelt / Landlock mode)

When not using container mode, configure host-level file access restrictions:
agents:
  - id: my-agent
    file_access:
      unrestricted: true
      allow_read: []
      allow_read_write: []
      deny: []
      allow_home_dotfiles: false
      allow_network: true

Configuration Fields

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

# Build the container image from configured packages
meepagateway container build

# Check container status
meepagateway container status

# List all container images
meepagateway container images

# Clean all container images
meepagateway container clean

Docker

# List isolation + container images
docker images | grep meepa

# Remove a stale isolation image (forces rebuild on next message)
docker rmi meepagateway-isolated:0.0.1-meepa-sandbox-abc123

# Remove all container images
docker rmi $(docker images -q meepa-sandbox)

Troubleshooting

”exec format error”

The isolation image contains a binary built for the wrong architecture. Delete the image and let it rebuild:
docker rmi meepagateway-isolated:*

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:
meepagateway container build
docker rmi meepagateway-isolated:*

SQLite disk I/O errors

Usually caused by a stale isolation image without proper HOME/TMPDIR settings. Delete and rebuild:
docker rmi meepagateway-isolated:*