Serve terminal sessions over the web with a simple CLI command. Blog post
Credit and Inspiration: This project was originally based on the genius web package, which uses
xterm.js. It has been rewritten to use a ghostty-web's WebAssembly-based terminal emulator, which provides better performance and native theme support.
It is, for the moment, temporarily based on a patched version of ghostty-web, because the current version has bugs and feature gaps that I needed to fill.
Coupled with agentbox, you can use it to keep track of several containerized AI coding agents, since it provides an easy way to expose terminal sessions via HTTP/WebSocket with automatic reconnection support:
- Web-based terminal - Access your terminal from any browser
- Mobile support - Works on iOS Safari and Android with on-screen keyboard modifier (experimental) and touch selection
- Session reconnection - Refresh the page and reconnect to the same session
- Full terminal emulation - Colors, cursor, and ANSI codes work correctly
- Customizable themes - 9 built-in themes (monokai, dracula, nord, etc.)
- Custom fonts - Configure terminal font family and size
- Scrollback history - Scroll back through terminal output (configurable)
- Auto-sizing - Terminal automatically resizes to fit the browser window
- Live screenshots - Dashboard shows real-time SVG screenshots of terminals
- CPU sparklines - Dashboard displays 30-minute CPU history for Docker containers
- SSE updates - Real-time screenshot updates via Server-Sent Events
- Simple CLI - One command to start serving
- No Authentication - this is meant to be used inside a dedicated container, and you should set up an authenticating reverse proxy like
authelia - No Encryption (TLS/HTTPS) - again, this is meant to be fronted by something like
traefikorcaddy
Install directly from GitHub:
pip install git+https://github.com/rcarmo/webterm.gitServe your default shell:
webtermServe a specific command:
webterm htopSpecify host and port:
webterm --host 0.0.0.0 --port 8080 bashCustomize theme and font:
webterm --theme dracula --font-size 18
webterm --theme nord --font-family "JetBrains Mono, monospace"Available themes: xterm (default), monokai, dark, light, dracula, catppuccin, nord, gruvbox, solarized, tokyo.
Then open http://localhost:8080 in your browser.
You can serve a dashboard with multiple terminal tiles driven by a YAML manifest:
- name: My Service
slug: my-service
command: docker logs -f my-serviceRun with:
webterm --landing-manifest landing.yamlWatch for Docker containers with webterm-command or webterm-theme labels and dynamically add/remove terminal sessions:
webterm --docker-watchWhen a container starts with either label, it automatically appears in the dashboard. When it stops, it's removed. Label values:
webterm-command: auto(or empty) - Opens a PTY via Docker exec API (override withWEBTERM_DOCKER_AUTO_COMMAND)webterm-command: <command>- Runs the specified commandwebterm-theme: <theme>- Sets the terminal theme for that container (xterm, monokai, dark, light, dracula, catppuccin, nord, gruvbox, solarized, tokyo). Invalid themes fall back totangoand the page background defaults to black.
Containers that only specify webterm-theme are still included and use the default auto command.
Environment Variables:
WEBTERM_DOCKER_USERNAME- Set to run Docker exec sessions as a specific user (default: root)WEBTERM_DOCKER_AUTO_COMMAND- Override the defaultautocommand (default:/bin/bash). Supports{container}placeholder for the container name.WEBTERM_SCREENSHOT_FORCE_REDRAW- When set totrue, send a SIGWINCH-style redraw before generating screenshots (default: false).
Example: Start containers and exec into them as developer user:
WEBTERM_DOCKER_USERNAME=developer webterm --docker-watchExample: Use tmux with per-container session names:
WEBTERM_DOCKER_AUTO_COMMAND="tmux new-session -ADs {container}" webterm --docker-watchThis creates a tmux session named after each container (e.g., my-webapp, redis, etc.) instead of a shared session name.
Example docker-compose.yaml:
services:
myapp:
image: myapp:latest
labels:
webterm-command: auto # Opens bash in container
webterm-theme: monokai
logs:
image: myapp:latest
labels:
webterm-command: docker logs -f myapp # Shows logs
webterm-theme: nordRequires: Docker socket access (-v /var/run/docker.sock:/var/run/docker.sock)
Point to a docker-compose file; services with the label webterm-command become tiles (and webterm-theme applies there too):
services:
db:
image: postgres
labels:
webterm-command: docker exec -it db psql
webterm-theme: gruvboxStart with:
webterm --compose-manifest compose.yamlIn compose mode, the dashboard displays CPU sparklines showing 30 minutes of container CPU usage history (requires access to Docker socket at /var/run/docker.sock).
- Live screenshots - Terminal thumbnails update in real-time via SSE when activity occurs
- Dynamic updates - In docker-watch mode, tiles appear/disappear as containers start/stop
- CPU sparklines - Mini charts showing container CPU usage (compose mode only)
- Tab reuse - Clicking the same tile reopens the existing browser tab
- Auto-focus - Terminals automatically receive keyboard focus on load
Usage: webterm [OPTIONS] [COMMAND]
Serve a terminal over HTTP/WebSocket.
COMMAND: Shell command to run in terminal (default: $SHELL)
Options:
-H, --host TEXT Host to bind to [default: 0.0.0.0]
-p, --port INTEGER Port to bind to [default: 8080]
-L, --landing-manifest PATH YAML manifest describing landing page tiles
(slug/name/command).
-C, --compose-manifest PATH Docker compose YAML; services with label
"webterm-command" become landing tiles.
-D, --docker-watch Watch Docker for containers with
"webterm-command" label (dynamic mode).
-t, --theme TEXT Terminal color theme [default: xterm]
Options: xterm, monokai, dark, light, dracula,
catppuccin, nord, gruvbox, solarized, tokyo
-f, --font-family TEXT Terminal font family (CSS font stack)
-s, --font-size INTEGER Terminal font size in pixels [default: 16]
--version Show the version and exit.
--help Show this message and exit.
| Endpoint | Description |
|---|---|
/ |
Dashboard (with manifest/docker-watch) or terminal view |
/ws/{route_key} |
WebSocket for terminal I/O |
/screenshot.svg?route_key=... |
SVG screenshot of terminal |
/cpu-sparkline.svg?container=... |
CPU sparkline SVG (compose mode) |
/tiles |
JSON list of current tiles (for dynamic dashboards) |
/events |
SSE stream for activity notifications |
/health |
Health check endpoint |
git clone https://github.com/rcarmo/webterm.git
cd webterm
# Install with dev dependencies via Makefile
make install-dev- Lint:
make lint - Format:
make format - Tests:
make test - Coverage (fail_under=78):
make coverage - Full check (lint + coverage):
make check - Bump patch version:
make bump-patch
The terminal UI is built with a patched version of ghostty-web, which provides Ghostty's VT100 parser via WebAssembly with native theme/palette support. This replaces the original xterm.js dependency used in earlier versions.
Key improvements over xterm.js:
- Native theme colors passed directly to WASM (no runtime color remapping)
- Smaller bundle size (~0.67 MB vs ~1.16 MB)
- IME input support for CJK languages
- Better Unicode and complex script rendering
The pre-built bundle is committed to the repo, so users can pip install without needing Node.js.
To rebuild the frontend after modifying terminal.ts:
# Requires Bun (https://bun.sh)
bun install
bun run build
# Or simply:
make bundleFor development with auto-rebuild:
make bundle-watch- WebSocket protocol (browser ↔ server) is JSON:
["stdin", data],["resize", {"width": w, "height": h}],["ping", data]. - Frontend source is in
src/webterm/static/js/terminal.ts. - Screenshots use pyte for ANSI interpretation and custom SVG rendering.
- CPU stats are read directly from Docker socket using asyncio (no additional dependencies).
- Python 3.9+
- Bun
- Linux or macOS
MIT License - see LICENSE for details.
- ghostty-web - Patched Ghostty terminal for the web (vendored fork with theme support)
- ghostty-web upstream - Original Ghostty terminal for the web
- pyte - PYTE terminal emulator (used for SVG screenshots)
