Skip to content

dstoc/cladding

Repository files navigation

Cladding lets you run an agent in a constrained container environment where network access is intentionally narrow:

In short: the agent cannot freely access the network; it can delegate commands to mcp-run via MCP or the run-remote binary, where any external network path is gated by command policy plus domain allowlists.

Getting Started

  • Install Podman

  • Install the cladding binary:

    podman run --rm -it \
      -v $HOME/.local/bin:/target/bin \
      rust:latest \
      cargo install --git https://github.com/dstoc/cladding cladding --root /target --force

    Alternative (install Rust locally first):

    # install Rust
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    # or on macOS with Homebrew
    brew install rust
    
    # from this repo
    cargo install --path . --bin cladding
    
    # or install directly from git
    cargo install --git https://github.com/dstoc/cladding cladding
  • Initialize local config:

    Config and mounts are stored in a .cladding directory.

    cladding init
    # or override generated name
    cladding init myproject
  • Edit files under .cladding/config/:

  • Build images and refresh host-mounted binaries (mcp-run, run-with-network) in .cladding/tools/bin:

    cladding build
  • Start the environment:

    cladding up
  • Run commands in the CLI container (workdir follows your host cwd relative to the directory containing .cladding):

    cladding run codex --yolo
    cladding run --env GEMINI_API_KEY gemini
  • Temporarily publish a TCP port from cli-app to the host while the project is running:

    cladding expose 3000
    cladding expose 3000 9000
    cladding expose list
    cladding expose stop 9000

    These mappings are runtime-only and are removed by cladding down and cladding destroy.

Configuring mounts

cladding.json supports a mounts list. Each entry has:

  • mount (required, absolute path in the container)
  • hostPath (optional, host bind mount; relative paths are resolved from .cladding/)
  • volume (optional, named volume; mutually exclusive with hostPath)
  • readOnly (optional, default false; ignored for volume mounts and forced true for empty mask mounts)
  • sandboxOnly (optional, default false; if true, mount applies only to sandbox-app)
  • ignore (optional, default false; when true, removes an existing default mount at the same mount path instead of replacing it)

If neither hostPath nor volume is set, an empty ConfigMap is used and mounted read-only - this is intended for masking or hiding underlying files, as used to hide .cladding by default. Mounts apply only to cli-app and sandbox-app (or only sandbox-app when sandboxOnly is true); other pod mounts are fixed.

Example:

{
  "mounts": [
    { "mount": "/home/user/workspace/.cache/npm", "volume": "npm-cache" },
    { "mount": "/opt/data", "hostPath": "../data", "readOnly": true },
    { "mount": "/tmp/isolated" },
    { "mount": "/opt/sandbox-only", "hostPath": "../sandbox-data", "sandboxOnly": true }
  ]
}

Default mounts (as if expressed via mounts):

{
  "mounts": [
    { "mount": "/opt/config", "hostPath": "config", "readOnly": true },
    { "mount": "/opt/tools", "hostPath": "tools", "readOnly": true },
    { "mount": "/home/user", "hostPath": "home" },
    { "mount": "/home/user/workspace", "hostPath": ".." },
    { "mount": "/home/user/workspace/.cladding" }
  ]
}

Default mounts may be overridden by adding an entry with the same mount value, or removed by adding an entry with the same mount and "ignore": true.

Architecture + Network Controls

flowchart TB
  subgraph C[cli-pod]
    CJ[cli-node: nftables jailer]
    CA[cli-app]
  end

  subgraph H[volumes]
    WS[.cladding/..]
    HOME[.cladding/home]
  end


  subgraph S[sandbox-pod]
    SJ[sandbox-node: nftables jailer]
    SA[sandbox-app: mcp-run :3000]
  end

  subgraph P[proxy-pod]
    PX[Squid proxy :8080]
  end

  WS --> CA
  WS --> SA
  HOME --> CA
  HOME --> SA

  CA -- MCP tool calls --> SA
  CA -- HTTP(S) via proxy --> PX
  SA -- HTTP(S) via proxy --> PX
  PX -- allowlisted domains only, ACL by source --> NET[(Internet)]

  CJ -. allow egress only to sandbox:3000 and proxy:8080 .-> CA
  SJ -. allow egress only to proxy .-> SA
Loading

Useful Commands

cladding init [name] [--update-scripts]  # initialize or update .cladding and config
cladding check        # verify required paths/images
cladding ps           # list running cladding projects
cladding run [--env KEY[=VALUE] ...] [cmd] # run a command in the cli-app container
cladding run-with-scissors [--env KEY[=VALUE] ...] [cmd] # run a command (not checked by policy) in the sandbox-app container
cladding expose <containerport> [hostport] # publish a cli-app TCP port to localhost
cladding expose list # show active published ports for the current project
cladding expose stop <hostport> # remove one published localhost port
cladding reload-proxy # reconfigure squid after domain-list edits
cladding down         # stop associated pods
cladding destroy      # force-remove running containers
cladding up           # starts the containers
podman logs -f <name>-proxy-pod-proxy           # view proxy logs
podman logs -f <name>-sandbox-pod-sandbox-app   # sandbox (mcp-run) logs

About

Constrained container environment for running agents

Topics

Resources

Stars

Watchers

Forks

Contributors