This document describes the .watermelon.toml configuration file format for Watermelon sandboxes.
The .watermelon.toml file defines how your project's sandbox VM is configured. Place this file in your project's root directory.
# Example .watermelon.toml
[vm]
image = "ubuntu-22.04"
[network]
allow = ["registry.npmjs.org", "github.com"]
[tools]
"node:20-slim" = ["node", "npm", "npx"]
[mounts]
# "~/.gitconfig" = { target = "/home/dev/.gitconfig" }
[ports]
forward = [3000, 8080]
[resources]
memory = "4GB"
cpus = 2
disk = "10GB"
[security]
enforcement = "log"
[ide]
command = "code"Configures the base virtual machine.
| Field | Type | Default | Description |
|---|---|---|---|
image |
string | "ubuntu-22.04" |
Base OS image for the VM |
Supported images:
ubuntu-22.04
[vm]
image = "ubuntu-22.04"Controls network access from the sandbox. Unknown outbound network behavior depends on [security].enforcement: discovery mode logs and allows, while strict modes block.
| Field | Type | Default | Description |
|---|---|---|---|
allow |
string[] | [] |
List of allowed domains/IPs |
Domain format:
- Plain domain:
"example.com" - Wildcard subdomain:
"*.example.com" - Domain with port:
"example.com:443" - IP address:
"192.168.1.1"
Security: Domains are parsed before rendering. Supported values are plain domains, wildcard subdomains without ports, IPv4 addresses, and plain domains or IPv4 addresses with TCP ports.
[network]
allow = [
# Package registries
"registry.npmjs.org",
"pypi.org",
"files.pythonhosted.org",
# Git hosting
"github.com",
"*.githubusercontent.com",
# Wildcards for subdomains
"*.huggingface.co",
]To completely block non-DNS network access, use an empty allow list with strict enforcement:
[network]
allow = []
[security]
enforcement = "fail"Per-process network rules. Each key is a process name, and the value is a list of additional domains that process can access (in addition to the general allow list).
| Field | Type | Description |
|---|---|---|
"<process-name>" |
string[] | Additional allowed domains for this process |
Behavior:
- Rules are additive: process-specific domains are added to the general
allowlist - Processes not listed use only the general
allowrules - Wildcards supported:
"*.example.com"
Implementation: Each listed process runs in a dedicated Linux network namespace with its own iptables rules. Wrapper scripts in /usr/local/bin/ transparently route the process through its namespace.
[network]
allow = ["registry.npmjs.org", "pypi.org"]
[network.process]
claude = ["api.anthropic.com", "*.anthropic.com"]
codex = ["api.openai.com"]
aider = ["api.anthropic.com", "api.openai.com"]Security: Process names cannot contain shell metacharacters (;|&$\\ /`).
Note: Requires VM reprovisioning (watermelon destroy && watermelon init) to apply changes.
Packages to install during VM provisioning. Each key corresponds to a package manager.
| Field | Type | Requires Tool | Install Command |
|---|---|---|---|
npm |
string[] | node image |
npm install -g <pkg> |
pip |
string[] | python image |
pip install <pkg> |
cargo |
string[] | rust image |
cargo install <pkg> |
go |
string[] | go or golang image |
go install <pkg> |
gem |
string[] | ruby image |
gem install <pkg> |
Validation:
- Package names cannot contain shell metacharacters (
;|&$\``) - Each package manager requires a matching tool image in
[tools] - If the package manager command is not found at provision time, provisioning fails
[tools]
"node:20-slim" = ["node", "npm", "npx"]
"python:3.12-slim" = ["python", "python3", "pip"]
[provision]
npm = ["@anthropic-ai/claude-code", "typescript"]
pip = ["aider-chat", "black"]Use case: Install AI coding assistants and development tools automatically:
[provision]
npm = ["@anthropic-ai/claude-code"] # Claude Code CLI
pip = ["aider-chat"] # Aider AI assistant
cargo = ["ripgrep", "fd-find"] # Fast search toolsDefines containerized tools available in the sandbox. Tools are run via nerdctl containers with host networking enabled.
| Field | Type | Description |
|---|---|---|
"image:tag" |
string[] | List of commands to expose from this container image |
Format: "<docker-image>:<tag>" = ["cmd1", "cmd2", ...]
Each command becomes available as a wrapper script in /usr/local/bin/ inside the VM.
[tools]
# Node.js tools
"node:20-slim" = ["node", "npm", "npx"]
# Python tools
"python:3.12-slim" = ["python", "python3", "pip"]
# Foundry (Ethereum development)
"ghcr.io/foundry-rs/foundry" = ["forge", "cast", "anvil", "chisel"]
# Go compiler
"golang:1.22" = ["go"]
# Rust toolchain
"rust:latest" = ["cargo", "rustc"]How it works:
- When you run
npm installin the sandbox, it executes:nerdctl run --rm -it --network=host -v /project:/project -w /project node:20-slim npm install
- The
--network=hostflag ensures ports bind to the VM's network - Lima's port forwarding exposes these ports to the host
Additional host paths to mount into the VM (beyond the project directory).
| Field | Type | Description |
|---|---|---|
"<host-path>" |
Mount | Mount configuration object |
Mount object:
| Field | Type | Default | Description |
|---|---|---|---|
target |
string | required | Path inside the VM |
mode |
string | "ro" |
Mount mode: "ro" (read-only) or "rw" (read-write) |
[mounts]
# Git config (read-only)
"~/.gitconfig" = { target = "/home/dev/.gitconfig" }
# SSH keys (read-only) - use with caution
"~/.ssh" = { target = "/home/dev/.ssh", mode = "ro" }
# npm auth tokens
"~/.npmrc" = { target = "/home/dev/.npmrc" }
# Shared cache directory (read-write)
"~/.cache/huggingface" = { target = "/home/dev/.cache/huggingface", mode = "rw" }Note: The project directory is always mounted at /project with read-write access.
Ports to forward from the VM to the host machine.
| Field | Type | Default | Description |
|---|---|---|---|
forward |
int[] | [] |
List of ports to forward |
Port requirements:
- Must be in range 1-65535
- Ports are forwarded bidirectionally (guest port = host port)
[ports]
# Single port
forward = [3000]
# Multiple ports
forward = [3000, 8000, 8080, 8545]Common ports by framework:
| Framework | Port |
|---|---|
| Vite | 5173 |
| Next.js | 3000 |
| Django | 8000 |
| FastAPI | 8000 |
| Anvil (Ethereum) | 8545 |
| Jupyter | 8888 |
| TensorBoard | 6006 |
VM resource allocation.
| Field | Type | Default | Description |
|---|---|---|---|
memory |
string | "2GB" |
RAM allocation |
cpus |
int | 1 |
Number of CPU cores (minimum: 1) |
disk |
string | "10GB" |
Disk size |
Size format: Number followed by unit (MB, GB, TB)
[resources]
memory = "4GB"
cpus = 2
disk = "15GB"Recommended settings by use case:
| Use Case | Memory | CPUs | Disk |
|---|---|---|---|
| Simple Node.js | 2GB | 1 | 10GB |
| React/Next.js | 4GB | 2 | 15GB |
| Smart contracts | 4GB | 2 | 15GB |
| Machine learning | 16GB | 4 | 50GB |
| Security audit | 2GB | 1 | 5GB |
Security policy configuration.
| Field | Type | Default | Description |
|---|---|---|---|
enforcement |
string | "log" |
How to enforce network policy |
Enforcement modes:
| Value | Behavior |
|---|---|
"log" |
Log the violation and allow the request |
"fail" |
Block the request and log an error |
"silent" |
Block the request silently |
"ask" |
Prompt for unknown TCP connections and persist always-allow choices |
[security]
# Development: see what's being blocked
enforcement = "log"
# Production/audit: strict blocking
enforcement = "fail"
# Quiet mode: block without noise
enforcement = "silent"
# Interactive mode: prompt on unknown TCP connections
enforcement = "ask"Configures the IDE for the watermelon code command.
| Field | Type | Default | Description |
|---|---|---|---|
command |
string | "code" |
IDE command to launch |
Supported IDE commands:
| IDE | Command |
|---|---|
| VS Code | code |
| Cursor | cursor |
| VSCodium | codium |
| VS Code Insiders | code-insiders |
[ide]
# VS Code (default)
command = "code"
# Cursor
command = "cursor"
# VSCodium
command = "codium"How it works:
When you run watermelon code, it executes:
<command> --remote ssh-remote+lima-<vmname> /projectThis opens your IDE connected to the sandbox VM via SSH Remote, directly in the /project directory.
Security: The IDE command is validated to prevent shell injection (no metacharacters allowed).
[vm]
image = "ubuntu-22.04"
[tools]
"node:20-slim" = ["node", "npm", "npx"]
[resources]
memory = "2GB"
cpus = 1
disk = "10GB"[vm]
image = "ubuntu-22.04"
[network]
allow = [
"registry.npmjs.org",
"pypi.org",
"files.pythonhosted.org",
"github.com",
"*.githubusercontent.com",
]
[tools]
"node:20-slim" = ["node", "npm", "npx"]
"python:3.12-slim" = ["python", "python3", "pip"]
[ports]
forward = [3000, 8000]
[resources]
memory = "8GB"
cpus = 4
disk = "20GB"
[security]
enforcement = "log"[vm]
image = "ubuntu-22.04"
[network]
allow = []
[tools]
"node:20-slim" = ["node", "npm", "npx"]
"python:3.12-slim" = ["python", "python3", "pip"]
[ports]
forward = []
[resources]
memory = "2GB"
cpus = 1
disk = "5GB"
[security]
enforcement = "fail"The configuration is validated at VM creation time:
-
Resources:
cpusmust be ≥ 1memoryanddiskmust be non-empty
-
Security:
enforcementmust be one of:log,fail,silent,ask
-
Network:
- Domains are parsed as plain hosts, wildcard subdomains, IPv4 addresses, or host/IP plus TCP port
- Wildcard domains cannot include ports
-
Ports:
- Each port must be in range 1-65535
Watermelon looks for .watermelon.toml in the current working directory when running commands. The VM name is derived from the project path to ensure consistent naming across sessions.