███╗ ███╗ █████╗ ██╗ ██╗██████╗ ███████╗
████╗ ████║██╔══██╗██║ ██║██╔══██╗██╔════╝
██╔████╔██║███████║██║ ██║██║ ██║█████╗
██║╚██╔╝██║██╔══██║██║ ██║██║ ██║██╔══╝
██║ ╚═╝ ██║██║ ██║╚██████╔╝██████╔╝███████╗
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
Ready-to-run sandbox appliance for agentic coding
Looking to get started? Use Maude Light — the current production implementation.
Maude Light is a lightweight WSL2 sandbox that deploys Ubuntu 24.04 with Claude Code in a single PowerShell command. See the light/ README for install instructions.
The full appliance described below (Ubuntu 26.04 VM/Docker with web-term, appmotel, and Traefik) is alpha-level and under active development.
maude is a pre-configured Ubuntu 26.04 appliance that gives developers a browser-based terminal, rapid web-app deployment, and an opinionated Claude Code environment — all in a single image you can run on WSL, VMware, KVM/Proxmox, or Docker.
- Browser terminal via web-term — full Linux shell in your browser using xterm.js + tmux (session persistence across disconnects)
- One-command app deployment via appmotel — deploy Python, Node.js, or Go apps from a Git URL; Traefik handles routing and TLS
- Non-root package management via mom — a setuid helper that lets users install packages without full sudo
- Claude Code ready — first-login wizard prompts to install Claude Code + the appmotel skill is pre-configured
- Multi-user — JIT Linux user provisioning on first login; each user gets their own isolated home
- WSL-safe — when running under WSL, Traefik binds to
127.0.0.1only (your laptop won't accidentally become a hosting platform) - Multiple deployment targets — WSL2, VMware, KVM/QEMU, Proxmox, Docker
Prerequisites: Windows Terminal and WSL must be installed first. See the Windows Setup Guide for step-by-step instructions.
Open Command Prompt (not PowerShell) and run:
curl -L -o maude-wsl.tar.gz https://github.com/dirkpetersen/maude/releases/latest/download/maude-wsl-ubuntu2604-v0.1.0.tar.gz
mkdir C:\maude
wsl --import maude C:\maude maude-wsl.tar.gz --version 2
wsl -d maudeWindows Terminal will automatically show maude as a profile after import. Your prompt will show maude@maude:~$.
On first boot, maude installs appmotel and deploys web-term automatically (~1 min). Then open: http://localhost:3000
docker run -d \
-p 3000:3000 \
-p 2222:22 \
-p 8080:8080 \
--name maude \
ghcr.io/dirkpetersen/maude:latest
# Open browser terminal
open http://localhost:3000Download the VM image from Releases:
| File | Format | Use with |
|---|---|---|
maude-ubuntu2604-<version>.ova |
OVA | VMware Workstation/Fusion/ESXi |
maude-ubuntu2604-<version>.qcow2 |
QCOW2 | KVM, Proxmox, QEMU |
# KVM quick start
qemu-system-x86_64 \
-enable-kvm \
-m 4G \
-smp 2 \
-drive file=maude-ubuntu2604-latest.qcow2,format=qcow2 \
-net nic -net user,hostfwd=tcp::3000-:3000,hostfwd=tcp::2222-:22 \
-daemonizeAfter boot, run sudo maude-setup to configure your domain name and enable HTTPS.
┌──────────────────────────────────────────────────────┐
│ System boots → maude-first-boot.service │
│ ├── Detects WSL or VM │
│ ├── Installs appmotel + Traefik │
│ ├── Deploys web-term as an app │
│ └── Configures firewall (VM only) │
│ │
│ User logs in → browser terminal at :3000 │
│ ├── ~/bin and ~/.local/bin added to PATH │
│ └── "Install Claude Code? [Y/n]" │
└──────────────────────────────────────────────────────┘
User browser
│ HTTP (or HTTPS after maude-setup)
▼
Traefik (:80 / :443)
[WSL: 127.0.0.1 only]
│
├─── web-term.yourdomain.com ──► web-term (Node.js :3000)
│ │ SSH
│ ▼
│ localhost:22 (sshd)
│ │
│ user's shell session (tmux)
│
└─── myapp.yourdomain.com ────► user app (Python/Node/Go)
managed by appmotel systemd service
mom (setuid) ──► apt-get / dnf [group-gated, audited]
Users: Each authenticated user gets a separate Linux account. Users share the appmotel service user for app deployments but have isolated home directories.
Run sudo maude-setup after first boot to configure:
| Setting | Description | Default |
|---|---|---|
MAUDE_HOSTNAME |
System hostname | maude |
MAUDE_BASE_DOMAIN |
Base domain for app routing | Auto-detected IP |
MAUDE_TLS_EMAIL |
Let's Encrypt email (optional) | — |
MAUDE_DEPLOY_TARGET |
vm, wsl, or docker |
Auto-detected |
Config file: /etc/maude/maude.conf
- Traefik listens on
127.0.0.1:80and127.0.0.1:8080 - Apps are accessible at
http://localhost(port 80) orhttp://localhost:8080 - No TLS setup required for local development
Once logged in, deploy any Python/Node.js/Go app from a Git repo:
# As the apps user (or via Claude Code with the appmotel skill)
sudo -u appmotel appmo add myapp https://github.com/you/myapp main
# Check status
sudo -u appmotel appmo status
# View logs
sudo -u appmotel appmo logs myapp 50
# Update from git
sudo -u appmotel appmo update myappappmotel auto-detects the language, builds the app, creates a systemd service, and configures Traefik routing. Apps are available at http://myapp.<your-domain>.
See the appmotel skill for Claude Code usage.
Every user is a member of the users group, which grants access to mom — a setuid helper that allows package installation without full sudo:
mom install ripgrep bat
mom update curl
mom refresh # apt-get update / dnf makecacheAll operations are logged to /var/log/mom.log (JSON) and syslog. New users created via maude-adduser are automatically added to the users group.
maude enforces a consistent PATH for every user:
~/bin → system dirs → ~/.local/bin
~/bin is always first so user scripts take priority over system commands. ~/.local/bin is always present exactly once at the end (tools like Claude Code install binaries there).
This is achieved by hooking maude-path.sh at the end of ~/.bashrc, which runs after Ubuntu's built-in PATH additions and after any tool that prepends to ~/.bashrc on install (such as Claude Code). The hook is placed in /etc/skel/.bashrc so new users get it automatically.
Changes to scripts can be tested at three speeds — no CI wait needed for most work.
make test # full test suite (PATH logic, script syntax, username validation, etc.)
make test-fast # stop on first failure
make lint # bash -n syntax check onlyIf you have maude-dev already imported, push changed scripts directly without rebuilding:
make wsl-update-scripts # copies all changed scripts into maude-dev
make wsl-test # smoke tests PATH, hostname, mom, users group
wsl -d maude-dev # open a shell to test interactivelyTo apply a PATH fix to an already-imported distro without rebuilding:
wsl -d maude -- bash -c 'printf "\n# maude path fix\nif [ -f /etc/profile.d/maude-path.sh ]; then . /etc/profile.d/maude-path.sh; fi\n" >> ~/.bashrc && exec bash -l && echo $PATH'Builds the same way as CI (Docker + ubuntu:plucky), outputs a tarball you import locally as maude-dev — doesn't touch your production maude distro.
Requires Docker running locally.
make build-wsl # builds output/maude-wsl-ubuntu2604-<version>.tar.gz
make wsl-import # registers it as 'maude-dev' (unregisters old one first)
make wsl-test # automated smoke tests
wsl -d maude-dev # interactive testingOverride the distro name or install path:
make wsl-import WSL_DISTRO=maude-test WSL_DIR=C:\maude-testTriggered automatically by pushing a version tag:
make tag VERSION=v0.2.0 # creates + pushes tag → triggers CI release workflowCI builds the WSL tarball, Docker image, and GitHub Release with SHA-256 checksums.
make build-docker # build Docker image locally
make run-docker # run Docker container (web-term :3000, SSH :2222)
make build-vm-kvm UBUNTU_ISO_URL=https://... # Packer KVM build (.qcow2)
make build-vm-vmware UBUNTU_ISO_URL=https://... # Packer VMware build (.ova)
make clean # remove output/ and local Docker imagesSee SECURITY.md for the full security analysis, threat model, and hardening checklist.
Key points:
- HTTP-only by default; enable HTTPS with
sudo maude-setup(Let's Encrypt via Traefik) - WSL deployments are
127.0.0.1-only — no external exposure PermitRootLogin noenforced; fail2ban active on VM deploymentsmomuses a setuid binary with strict environment sanitization and a deny list- Report vulnerabilities privately via GitHub Security Advisories
| Project | Role | Repo |
|---|---|---|
| web-term | Browser terminal (xterm.js + SSH + tmux) | dirkpetersen/web-term |
| appmotel | PaaS: Git→systemd→Traefik deployment | dirkpetersen/appmotel |
| mom | Non-root package management (setuid Rust) | dirkpetersen/mom |
- Fork and clone the repo
- Run
make testto verify your environment - Make changes, add tests in
tests/ - Test interactively with
make build-wsl && make wsl-import && make wsl-test - Open a PR — CI runs the test suite and builds the WSL image automatically
MIT © 2026 Dirk Petersen