Repository navigation
feat: Support host directory mounts (hostPath/PVC) via CLI for long-running sandboxes #500
Description
Problem
OpenShell sandboxes currently have no mechanism for live bidirectional access to host directories. File transfer is limited to one-time --upload / sandbox upload (tar-over-SSH copies). For services that continuously read/write host data (databases, session logs, config files), sandbox-internal copies go stale immediately.
This prevents using OpenShell's security enforcement (Landlock, seccomp, network policies) for persistent agent runtimes — the primary production use case for OpenClaw deployments.
Proposed Design
1. New --mount CLI flag
Add a --mount flag to sandbox create accepting host:container[:mode] triples:
openshell sandbox create \
--from ./Dockerfile \
--policy ./policy.yaml \
--mount /opt/data/config:/sandbox/config:ro \
--mount /opt/data/client:/sandbox/client:rw \
--forward 18789 \
-- node /opt/app/dist/index.js2. CLI parsing in run.rs
The sandbox_create function in crates/openshell-cli/src/run.rs builds a CreateSandboxRequest with a SandboxSpec. Add a mounts field:
// New CLI argument
#[arg(long, value_name = "HOST:CONTAINER[:MODE]")]
mount: Vec<String>,
// Parsed into structured mount specs
struct MountSpec {
host_path: String,
container_path: String,
read_only: bool, // default true unless `:rw` specified
}3. Server-side pod spec injection
crates/openshell-server/src/sandbox/mod.rs already has apply_supervisor_sideload() which injects hostPath volumes into pod specs. The same pattern applies:
// Existing pattern (supervisor binary mount):
// volume: hostPath /opt/openshell/bin -> /opt/openshell/bin (ro, DirectoryOrCreate)
// New pattern (user-requested mounts):
// For each --mount flag, inject:
// - A Volume with hostPath source
// - A VolumeMount on the sandbox container
// - read_only based on mode flagThe SandboxSpec already has a volume_claim_templates field that gets serialized to K8s resources — user mounts follow the same serialization path.
4. Policy integration
Mounted paths should respect filesystem_policy. A mount at /sandbox/client:rw still requires /sandbox/client in the policy's read_write list. Landlock enforcement applies on top of the mount — the mount makes the data available, the policy controls what the sandbox process can do with it.
This means mounts without matching policy entries are silently inaccessible (deny-by-default preserved).
5. K3s hostPath handling
The gateway's K3s cluster runs inside a Docker container. For hostPath volumes to reach the actual host filesystem, the gateway container itself needs the host path mounted. scripts/cluster-entrypoint.sh (or equivalent bootstrap) would need to propagate --mount paths as Docker bind mounts on the gateway container.
Alternatives Considered
--upload/--syncat creation time: One-time copy, immediately stale for databases and session logs that change every minute. Not suitable for live services.- Periodic sync via cron inside sandbox: Adds complexity, introduces sync lag, doesn't work for SQLite databases (WAL corruption on copy-during-write).
- NFS/FUSE mount inside sandbox: Requires additional infrastructure, breaks Landlock enforcement model, adds attack surface.
- Run service outside OpenShell: Loses all security enforcement (Landlock, seccomp, network policies). This is what users are forced to do today.
- inotify + rsync daemon: Complex, fragile, doesn't handle bidirectional writes, latency on large files.
Agent Investigation
Codebase paths examined:
crates/openshell-cli/src/run.rs—sandbox_create()buildsCreateSandboxRequestwithSandboxSpec { gpu, policy, providers, template }. No mount/volume field currently.crates/openshell-server/src/sandbox/mod.rs—apply_supervisor_sideload()already injects hostPath volumes + volumeMounts into pod specs. Pattern exists and works.crates/openshell-server/src/sandbox/mod.rs—SandboxSpechasvolume_claim_templatesfield, serialized to K8s JSON. Infrastructure for volume specs exists.deploy/kube/manifests/agent-sandbox.yaml— CRD definesvolumeMountsproperties in container spec (truncated in YAML but schema allows it).examples/sync-files.md— Documents tar-over-SSH upload/download. Confirms no live mount mechanism exists.examples/bring-your-own-container/README.md— Custom Dockerfile support works, but no mount flags documented.
Use Case
Running an OpenClaw agent gateway (19 agents, continuous cron jobs) that needs live read/write access to:
- Agent config and session state (
.openclaw/) - Client databases and content (
client/) - Product code as read-only reference (
core/)
Currently forced to use plain Docker without OpenShell's security enforcement because the sandbox cannot access live host data.
Activity
aiwh-cli commentedon Mar 20, 2026
Updated the issue with a concrete design proposal after investigating the codebase. The server-side plumbing for hostPath volumes already exists in
apply_supervisor_sideload()— the main gap is exposing it through the CLI and propagating host paths through the K3s gateway container. Happy to discuss the design or contribute a PR if the approach looks right.sandys commentedon Mar 22, 2026
we need /dev/fuse access for something like agentfs (https://github.com/tursodatabase/agentfs).
we save .claude (and all memory) to a fuse-mounted filesystem that persists to a database.
i suppose this is going to be a very popular usecase. this is fairly important.
aiwh-cli commentedon Mar 22, 2026
@sandys great call — FUSE-backed persistence is a clean solution for agent memory. Your use case actually fits naturally as an extension of this proposal.
The
--mountdesign in the issue description covers bind mounts (hostPath → container path). To support AgentFS / FUSE filesystems, we'd need two additions on top:--device /dev/fuse— pass the FUSE device into the sandbox container. K8s pod specs support this viacontainers[].securityContext.capabilities.add: ["SYS_ADMIN"]or the more targetedFUSEcapability, plus a hostPath volume for/dev/fuse.mount(). FUSE needsmount,umount2, and the/dev/fuseioctl. This could be scoped in the policy YAML rather than requiring full privileged mode.The implementation path stays the same —
apply_supervisor_sideload()already injects volumes into pod specs, and the same pattern handles device mounts. A--deviceflag alongside--mountkeeps the API clean:openshell sandbox create \ --mount /opt/data:/sandbox/data:rw \ --device /dev/fuse \ --policy ./policy.yaml \ -- node /opt/app/dist/index.jsPolicy integration stays deny-by-default — you'd need
devices: ["/dev/fuse"]andcapabilities: ["SYS_ADMIN"]explicitly in the policy YAML for it to work.Between bind mounts (our use case — live agent runtime with host databases) and FUSE mounts (your use case — transparent DB-backed persistence), this covers the two main patterns for stateful agents in sandboxes.
@drew @johntmyers — would love some feedback on this design direction. The server-side plumbing already exists in
apply_supervisor_sideload(), the main gap is CLI surface and K3s gateway propagation. Happy to put up a PR if the approach looks right.drew commentedon Mar 23, 2026
Thanks for feedback. We wanted to avoid exposing bidirectional sync for security reasons. It also limits our ability to deploy this stack to the cloud.
If there are improvements to the upload/download feature that go through the gateway we can consider those. Please open a discussion and we can continue the conversation there.
feat(policy): add declarative host mount schema, validation, and CLI (N…
feat(policy): add declarative host mount schema, validation, and CLI (N…