Run commands in an isolated, disposable Linux VM using Gondolin CLI. Use when testing untrusted code, running installations, or needing a clean isolated environment.
Use Gondolin to run commands in a disposable Linux micro-VM. The VM is isolated from the host network and filesystem by default.
| Command | Purpose |
|---|---|
exec | Run a command in a fresh disposable VM |
bash | Start an interactive shell session |
list | List running VM sessions |
attach | Attach to a running VM session |
snapshot |
| Snapshot a running VM and stop it |
image | Manage local image refs (ls, pull, tag, inspect) |
# Run a simple command
pnpx @earendil-works/gondolin exec -- uname -a
# Start an interactive shell
pnpx @earendil-works/gondolin bash
# Mount project and run commands
pnpx @earendil-works/gondolin exec --mount-hostfs "$PWD:/workspace" -- sh -lc 'cd /workspace && npm test'
Each gondolin exec creates a fresh VM, runs your command, and tears down. Changes don't persist unless you mount host directories. Use bash for persistent interactive sessions.
--allow-host to restrict to specific hostsThis is Gondolin's key security feature. Use --host-secret to inject credentials that the guest can use but never see.
Run a command in a fresh disposable VM.
gondolin exec [options] -- CMD [ARGS...]
VFS Options:
--mount-hostfs HOST:GUEST[:ro] - Mount host directory (append :ro for read-only)--mount-memfs PATH - Create memory-backed mount--image IMAGE - Guest image selector (asset dir, build id, or name:tag)--vmm BACKEND - VM backend: qemu (default) or krunNetwork Options:
--allow-host HOST - Allow HTTP requests to host (repeatable)--host-secret NAME@HOST[,HOST...][=VALUE] - Add secret for specified hosts--dns MODE - DNS mode: synthetic (default), trusted, or open--dns-trusted-server IP - Trusted resolver IPv4 (trusted mode)--tcp-map SPEC - Map guest HOST[:PORT] to upstream HOST:PORT (GUEST_HOST[:PORT]=UPSTREAM_HOST:PORT)--ssh-allow-host HOST[:PORT] - Allow outbound SSH (default port 22)--ssh-agent [SOCK] - Use ssh-agent for host-side SSH auth--ssh-known-hosts PATH - OpenSSH known_hosts file for upstream verification--ssh-credential SPEC - Host-side SSH key (HOST[:PORT]=PATH or USER@HOST[:PORT]=PATH)--disable-websockets - Disable WebSocket upgradesOther:
--env KEY=VALUE - Set environment variable (repeatable, for non-secrets)--cwd PATH - Set working directoryStart an interactive shell session in a VM. Press Ctrl-] to detach.
gondolin bash [options] [-- COMMAND [ARGS...]]
Supports all exec options plus:
--resume ID_OR_PATH - Resume from a snapshot ID or .qcow2 path--listen [HOST:PORT] - Start host ingress gateway (default: 127.0.0.1:0)--ssh - Enable SSH access via localhost port forward--ssh-user USER - SSH username (default: root)--ssh-port PORT - Local listen port (default: 0 = ephemeral)--ssh-listen HOST - Local listen host (default: 127.0.0.1)# Interactive shell with project mounted
gondolin bash --mount-hostfs "$PWD:/workspace"
# Resume from snapshot
gondolin bash --resume 4a8f2b0c
gondolin bash --resume /tmp/my-snapshot.qcow2
# With ingress gateway
gondolin bash --listen 127.0.0.1:3000
List active VM sessions.
gondolin list [--all]
--all - Show stale/dead sessions tooAttach to a running VM session.
gondolin attach <SESSION_ID> [options] [-- COMMAND [ARGS...]]
Options: --cwd PATH, --env KEY=VALUE
Default command: bash -i (fallback: /bin/sh -i)
Create a disk snapshot of a running VM and stop it.
gondolin snapshot <SESSION_ID> [options]
--output PATH - Path for the snapshot .qcow2 file--name NAME - Snapshot name (default: output path only)Manage local Gondolin images.
gondolin image ls # List local image refs
gondolin image import <ASSET_DIR> [--tag REF] # Import guest assets
gondolin image tag <SOURCE> <TARGET> # Create/update a ref
gondolin image inspect <SELECTOR> # Show image details
gondolin image pull <SELECTOR> # Pull from registry
Add --arch aarch64|x86_64 to target a specific architecture.
Use --mount-hostfs HOST:GUEST[:ro] to expose host directories to the VM.
# Mount current directory at /workspace (read-write)
gondolin exec --mount-hostfs "$PWD:/workspace" -- ls /workspace
# Mount read-only (important for sensitive files)
gondolin exec --mount-hostfs "$PWD:/workspace:ro" -- cat /workspace/.env
# Multiple mounts
gondolin exec --mount-hostfs /data:/data:ro --mount-hostfs "$PWD:/workspace" -- ls /data
HOST:GUEST - read-write mountHOST:GUEST:ro - read-only mount# Allow specific host
gondolin exec --allow-host api.github.com -- curl -sS https://api.github.com/rate_limit
# Allow multiple hosts
gondolin exec --allow-host api.github.com --allow-host api.openai.com -- curl ...
# Wildcards supported
gondolin exec --allow-host "*.github.com" -- curl https://api.github.com/...
This is how you safely use API tokens inside the VM:
# Secret injection - host reads from $GITHUB_TOKEN env var
gondolin exec \
--host-secret [email protected] \
-- curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user
# With explicit value
gondolin exec \
--host-secret [email protected]=ghp_xxxx \
-- curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user
How it works:
$GITHUB_TOKEN is a random placeholder (e.g., GONDOLIN_SECRET_abc123)gondolin exec \
--host-secret [email protected] \
--host-secret [email protected] \
-- curl -u "$USER:$PASS" https://example.com/private
The host handles base64 encoding automatically.
# Set environment variables (for non-secret values)
gondolin exec --env NODE_ENV=production --env DEBUG=* -- node app.js
# Set working directory
gondolin exec --mount-hostfs "$PWD:/workspace" --cwd /workspace -- npm start
Important: Don't use --env for secrets! Use --host-secret instead.
Create ephemeral in-memory mounts:
# Create a temp directory that disappears when VM exits
gondolin exec --mount-memfs /tmp -- ls /tmp
Useful for build artifacts you don't need to keep.
Since each exec starts a fresh VM, chain commands with &&:
# Install dependencies then run tests
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
-- sh -lc 'cd /workspace && npm install && npm test'
# Multi-step workflow
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
-- sh -lc 'cd /workspace && npm run build && npm run lint'
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
--allow-host registry.npmjs.org \
-- sh -lc 'cd /workspace && npm install'
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
-- sh -lc 'source ~/.nvm/nvm.sh && nvm use 20 && npm test'
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
--allow-host api.example.com \
--host-secret [email protected] \
-- sh -lc 'cd /workspace && ./scripts/deploy.sh'
gondolin exec -- apk list --installed
# Start bash with SSH enabled
gondolin bash --ssh --mount-hostfs "$PWD:/workspace"
# Or snapshot for later resume
gondolin snapshot <SESSION_ID> --output /tmp/my-env.qcow2
gondolin bash --resume /tmp/my-env.qcow2
Don't pass secrets via --env - They're visible in the VM!
# BAD - secret exposed in VM
gondolin exec --env API_KEY=real_secret -- curl ...
# GOOD - secret stays on host
gondolin exec --host-secret [email protected] -- curl ...
Don't mount your entire home directory - You'll expose SSH keys, credentials, etc.
Don't assume network needs restricting - By default, HTTP/HTTPS to any host is allowed. Use --allow-host only if you want to limit egress.
Don't expect persistence - Each exec is a fresh VM. Use --mount-hostfs for files you need, or use bash + snapshot for persistent sessions.
Check stdout/stderr in the result for debugging.
brew install qemu on macOS, apt install qemu-system-* on Linux) OR libkrun (--vmm krun)~/.cache/gondolin/# Basic
gondolin exec -- uname -a
# Interactive shell
gondolin bash
# With project mounted
gondolin exec --mount-hostfs "$PWD:/workspace" -- ls /workspace
# With network access
gondolin exec --allow-host api.github.com -- curl https://api.github.com
# With secret (SAFE)
gondolin exec --host-secret [email protected] -- curl -H "Authorization: Bearer $TOKEN" https://api.github.com
# Full workflow
gondolin exec \
--mount-hostfs "$PWD:/workspace" \
--allow-host registry.npmjs.org \
--allow-host api.github.com \
--host-secret [email protected] \
-- sh -lc 'cd /workspace && npm install && npm test'
# List sessions
gondolin list
# Snapshot and resume
gondolin snapshot <SESSION_ID> --output /tmp/snap.qcow2
gondolin bash --resume /tmp/snap.qcow2