Skip to content

Run NVIDIA OpenShell on a remote VM

Agents in OpenShell sandboxes, inside a VM of their own. No API keys on it.

Deploy on exe.dev

Shelley handles the setup. About 10 minutes.

Prefer a terminal?
curl -fsSL https://exe-staging.dev/deploy/openshell/prompt.txt | ssh exe.dev new --prompt=/dev/stdin

OpenShell is NVIDIA's runtime for running agents safely. Each agent gets a sandbox with no network and limited file access. When it tries to reach something new, OpenShell blocks it and drafts a rule for you to approve.

Here it runs on an exe.dev VM: a second boundary, with its own kernel, around the sandboxes. Agents reach models through exe.dev's LLM integration, so the VM holds no API keys.

What you get#

  • The OpenShell gateway running as a systemd user service, back after a reboot, with openshell status saying Connected
  • An agent image, agents:local, with Claude Code and Codex set up for exe.dev's LLM integration, and no API key on the VM
  • An exe-llm provider that lets sandboxes reach models through a relay on the VM, and nothing else on the internet
  • A test sandbox in which Claude Code answered, then deleted
What's running, and where
Private ssh my-openshell.exe.xyz the openshell CLI and your agents' terminals
Private https://my-openshell.exe.xyz:8080/ optional, a web app running in a sandbox
my-openshell.exe.xyz exe.dev VM
OpenShell gateway openshell-gateway, a systemd user service; the control plane for sandboxes, policies, and providers :17670~/.local/state/openshell
Sandboxes one Docker container each (or a microVM), from an image with Claude Code and Codex; no network of their own /sandbox in each sandbox
LLM relay nginx on 127.0.0.1:11435, passing sandbox model calls to exe.dev's LLM integration

Deploy with Shelley#

Click Deploy and Shelley will set up OpenShell on a new VM, then check that it works. No config-file wrangling required.

Prefer a terminal?
curl -fsSL \
  https://exe-staging.dev/deploy/openshell/prompt.txt \
  | ssh exe.dev new --prompt=/dev/stdin
What Shelley is told
Set up OpenShell on this VM by following the exe.dev recipe at https://exe-staging.dev/deploy/openshell.md from top to bottom.

The recipe's example VM name is my-openshell. This VM's name is the output of `hostname`: use it wherever the recipe writes my-openshell. Skip the step that creates the VM; you are on it.

You are done when all of these are true. Check each one yourself:
- The OpenShell gateway running as a systemd user service, back after a reboot, with `openshell status` saying `Connected`
- An agent image, `agents:local`, with Claude Code and Codex set up for exe.dev's LLM integration, and no API key on the VM
- An `exe-llm` provider that lets sandboxes reach models through a relay on the VM, and nothing else on the internet
- A test sandbox in which Claude Code answered, then deleted

Run "Set up by hand" from "On the VM", skipping VM creation and SSH,
including "Check it". Don't start interactive agents or keep any
sandbox. Finish by telling the owner to SSH in and run the command in
"Start an agent".

Finish with the URLs I should open.

Also at /deploy/openshell/prompt.txt. The recipe it follows is /deploy/openshell.md.

Start an agent#

On the VM (ssh my-openshell.exe.xyz):

openshell sandbox create --name claude --from agents:local --provider exe-llm -- claude

Detach with Ctrl-P Ctrl-Q; Claude keeps running. Reattach with openshell sandbox connect claude. Use -- codex for Codex, or no command for a shell. Your work is in /sandbox.

Other commands: openshell sandbox list, openshell sandbox delete <name>, and openshell term (a dashboard).

Approve network access#

A new sandbox reaches the models and nothing else. When something is blocked, say a git clone, OpenShell drafts a rule:

openshell rule get claude --status pending
openshell rule approve claude --chunk-id <chunk-id>

The retry works without a restart. Rules apply to one program: allowing github.com for git doesn't allow it for curl. openshell rule approve-all claude approves everything pending, and openshell logs claude shows each connection. See OpenShell's policy docs.

Set it up#

Set up by hand

On your computer (any free VM name):

ssh exe.dev new --name=my-openshell
ssh my-openshell.exe.xyz

On the VM, start Docker at boot and add the LLM relay:

sudo systemctl enable --now docker.service
sudo curl -fsSL -o /etc/nginx/conf.d/exe-llm-relay.conf \
  https://exe-staging.dev/deploy/openshell/exe-llm-relay.conf
sudo systemctl enable nginx && sudo systemctl restart nginx
curl -s localhost:11435/v1/models | head -c 80; echo

Expect JSON listing models. Install OpenShell (deb skips a snap check that hangs for two minutes):

curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh \
  | OPENSHELL_INSTALL_METHOD=deb sh
openshell status

Expect Status: Connected. Add the exe-llm provider and build the agent image (about two minutes):

mkdir -p ~/openshell && cd ~/openshell
for f in exe-llm.yaml Dockerfile; do
  curl -fsSLO https://exe-staging.dev/deploy/openshell/$f
done
openshell profile import --file exe-llm.yaml
openshell provider create --name exe-llm --type exe-llm
docker build -t agents:local .

Check it#

openshell sandbox create --name check --no-keep --from agents:local --provider exe-llm \
  -- claude -p "Reply with exactly: claude ok"

Expect claude ok, then Deleted sandbox check.

How it works#

  • The gateway (openshell-gateway, a systemd user service) stores sandboxes, policies, and providers in ~/.local/state/openshell.
  • A supervisor beside each sandbox routes all of its traffic and checks every connection against the policy.
  • The sandbox is a Docker container with no network interface, plus Landlock and seccomp.

Sandboxes can't reach llm.int.exe.xyz directly, because OpenShell blocks link-local addresses. A keyless nginx relay (exe-llm-relay.conf) listens on the VM's loopback, and exe-llm.yaml lets the agents reach it at host.openshell.internal:11435. The Dockerfile installs Claude Code and Codex and points both at the relay.

Open a web app in a sandbox#

Expose a port, here 3000 in the sandbox claude:

openshell service expose claude 3000

That URL works only on the VM. To serve it at https://my-openshell.exe.xyz:8080/, private to you through the HTTPS proxy:

sudo tee /etc/nginx/conf.d/openshell-web.conf > /dev/null <<'EOF'
server {
    listen 8080;
    location / {
        proxy_pass http://127.0.0.1:17670;
        proxy_set_header Host default--claude.openshell.localhost:17670;
        proxy_set_header Origin "";
        proxy_set_header Referer "";
        proxy_set_header Sec-Fetch-Site "";
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
    }
}
EOF
sudo nginx -t && sudo systemctl reload nginx

Clearing the origin headers is needed because OpenShell rejects cross-origin requests.

Run sandboxes as microVMs#

Switch to microVMs

Each sandbox can run in its own small VM (libkrun on KVM) instead of a container. Limits: 2 vCPUs, 2 GB of memory, and 4 GB of disk each; about a minute to start the first one. After a reboot, sandboxes show Error until you run openshell sandbox stop <name> and then start (files are kept). Delete Docker sandboxes before you switch, because the gateway forgets them.

sudo usermod -aG kvm exedev
mkdir -p ~/.config/openshell
cat > ~/.config/openshell/gateway.toml <<'EOF'
[openshell]
version = 2

[openshell.gateway]
compute_driver = "vm"
EOF
openshell-gateway config preflight --path ~/.config/openshell/gateway.toml
sudo systemctl restart user@$(id -u).service

Then run "Check it" again. To switch back, delete gateway.toml and restart the same way.

Back up and update#

Back up, update, and uninstall

Copy a sandbox's files to the VM (or push them from the sandbox). Gateway state is in ~/.local/state/openshell.

openshell sandbox download claude /sandbox ~/backup/claude

Update OpenShell by running the installer again (read the release notes first; running sessions are interrupted):

curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh \
  | OPENSHELL_INSTALL_METHOD=deb sh

Update the agents with docker build --no-cache -t agents:local ~/openshell; only new sandboxes use the new image. To uninstall:

systemctl --user disable --now openshell-gateway
sudo apt remove openshell
rm -rf ~/.local/state/openshell

Troubleshooting#

  • openshell status can't connect: journalctl --user -u openshell-gateway -n 50.
  • Agent can't reach its API: run curl -s localhost:11435/v1/models to check the relay. Check openshell policy get <name> --full for _provider_exe_llm; if it's missing, openshell sandbox provider attach <name> exe-llm. If openshell logs <name> shows DENIED <program>, add that program to binaries in exe-llm.yaml.
  • Installs fail inside a sandbox: that's the policy. Approve the pending rule (above).

Frequently asked questions#

Why put OpenShell in a VM when it's already a sandbox?

Defense in depth. OpenShell's sandboxes share a kernel with the gateway. On exe.dev that kernel belongs to a VM holding none of your keys or files, so an escape gets nothing, and deleting the VM removes it.

Where are the API keys?

Not on the VM. exe.dev's LLM integration adds them outside it and bills your account. Keys you give other OpenShell providers stay with the gateway; sandboxes see placeholders.

Which agents does it run?

Whatever is in the image: here, Claude Code and Codex. Build your own and pass it to --from. Programs that call models need their path under binaries in exe-llm.yaml.

Do sandboxes survive a reboot?

Yes, with their /sandbox files and approved rules, Ready again in about a minute. The agent restarts with a new session. Deleting a sandbox deletes its files.

Is OpenShell stable?

It's pre-1.0 with weekly releases, and 0.1.0 broke compatibility with earlier versions. We tested 0.1.6. Read the release notes before updating.

Updated Oct 31, 2026 · tested end to end Oct 31, 2026 · Markdown · manifest.json · Something wrong? Tell us