Skip to content

andresmorales07/hatchpod

Repository files navigation

📦 Hatchpod

A persistent, self-hosted Claude Code workstation you can access from anywhere.

CI Release Container License

Run Claude Code on a server, VPS, or homelab — then connect via SSH, web browser, Mosh, or Tailscale VPN from wherever you are. Your files, credentials, MCP servers, dotfiles, and Docker images all persist across restarts.

Think of it as your personal cloud dev machine with Claude Code built in.

Web UI

A mobile-friendly web interface for Claude Code — start sessions, chat with Claude, browse files, and run shell commands, all from any browser.

Session list Chat view
New session Tool calls
Embedded terminal Settings

Highlights:

  • Session history — all past Claude sessions are listed in the sidebar, grouped by date, searchable by prompt. Pick up any conversation where you left off.
  • Rich message rendering — responses use full Markdown with syntax-highlighted code blocks, bold/italic, numbered lists, and inline code.
  • Tool call cards — every Bash, Read, Edit, Write, and other tool call is shown as a collapsible card with the command and output. Groups of consecutive calls from the same tool are collapsed into a single summary.
  • Syntax-highlighted diffsWrite and Edit tool results render as side-by-side file diffs with language-aware highlighting.
  • Subagent cards — when Claude spawns a subagent, the nested session appears inline as a collapsible card.
  • Live thinking indicator — animated spinner shows extended thinking in progress; reasoning text streams in real time.
  • Embedded terminal — full xterm.js terminal in the browser, connected to a real PTY inside the container via WebSocket. No SSH client needed.
  • Slash commands — type / in the composer for autocomplete of common Claude Code slash commands.
  • Tool approval UI — when Claude requests a permission (e.g. running a command), an inline approval card appears with Accept/Reject buttons. No need to watch the terminal.
  • Git status bar — the bottom of each chat shows the current branch and number of changed files.
  • Model & effort picker — switch between Haiku, Sonnet, and Opus per-session, or set a default in Settings. Effort level (Low → Max) is configurable; Max is Opus-only.
  • Dark/light theme — toggle in Settings; preference is persisted locally.
  • Mobile-friendly — responsive layout with a bottom nav bar and swipe gestures on small screens.
  • Interactive API docs/api/docs serves a Scalar UI with the full OpenAPI 3.1 spec. Try endpoints directly in the browser.

Why Hatchpod?

Unlike ephemeral sandboxes (like Docker Sandboxes) that spin up for a single task and disappear, Hatchpod is a long-lived workstation.

Ephemeral Sandboxes Hatchpod
Lifecycle Task-scoped, disposable Persistent — pick up where you left off
Access Local only SSH, Mosh, Web UI, Tailscale VPN
Customization Pre-set image Full Linux env with sudo, dotfiles, any tooling
Docker-in-Docker Limited or none Full DinD via Sysbox
Requires Docker Desktop Any Linux host with Docker Engine

Quick Start

# 1. Clone and configure
git clone https://github.com/andresmorales07/hatchpod.git
cd hatchpod
cp .env.example .env            # edit .env to set your passwords

# 2. Start (pulls the prebuilt image — no build step needed)
docker compose up -d

# 3. Connect
ssh -p 2222 hatchpod@localhost    # password is SSH_PASSWORD from .env

# 4. Authenticate Claude Code (first time only)
claude                          # follow the login link that appears

No Sysbox? The default docker-compose.yml sets runtime: sysbox-runc for Docker-in-Docker. If you don't have Sysbox installed, create a one-line override:

echo 'services: { hatchpod: { runtime: runc } }' > docker-compose.override.yml
docker compose up -d

Everything except docker commands inside the container will work without Sysbox.

What's Included

CategorySoftwarePurpose
🤖 AIClaude CodeAnthropic's CLI agent
Web UI + APIClaude Code web interface (Tailwind CSS v4 + shadcn/ui) and REST/WebSocket API (port 8080)
RuntimesNode.js 20 LTSMCP servers (npx)
Python 3 + venvMCP servers (uvx)
📦 Package MgrsnpmNode packages (global prefix persisted)
uv / uvxPython packages and tool runner
🐳 ContainersDocker Engine + ComposeDocker-in-Docker (requires Sysbox on host)
🌐 AccessOpenSSH serverRemote access (port 2222)
moshResilient mobile shell (UDP 60000-60003)
TailscaleVPN access (opt-in, set TS_AUTHKEY)
🔧 Dev ToolsgitVersion control
GitHub CLI (gh)GitHub operations
curl, jqHTTP requests and JSON processing
make, g++C++ build tools for native Node.js addons (node-pty)
🖥️ Systems6-overlay v3Process supervision
sudo (passwordless)Root access for hatchpod user

Access Methods

Connect from any machine — all access methods work both locally and remotely.

SSH (port 2222)

ssh -p 2222 hatchpod@localhost

Use your SSH_PASSWORD to authenticate, or add your public key:

ssh-copy-id -p 2222 hatchpod@localhost

Web UI + API (port 8080)

Mobile-friendly web interface for Claude Code. Works on phones, tablets, and desktops.

# Web UI
open http://localhost:8080

# API docs (interactive, no auth required)
open http://localhost:8080/api/docs

# REST API
curl -H "Authorization: Bearer $API_PASSWORD" http://localhost:8080/api/sessions

# Create a session
curl -X POST -H "Authorization: Bearer $API_PASSWORD" \
     -H "Content-Type: application/json" \
     -d '{"prompt":"What files are in the workspace?"}' \
     http://localhost:8080/api/sessions

# OpenAPI spec
curl http://localhost:8080/api/openapi.json

API endpoints: GET /healthz, GET /api/openapi.json, GET /api/docs, POST /api/sessions, GET /api/sessions, GET /api/sessions/:id, DELETE /api/sessions/:id, GET /api/sessions/:id/history, GET /api/sessions/:id/messages, GET /api/browse, GET /api/config, GET /api/providers, GET /api/git/status, GET /api/settings, PATCH /api/settings. Chat streaming at WS /api/sessions/:id/stream; embedded terminal at WS /api/terminal/stream.

Mosh (UDP 60000-60003)

Resilient connection that survives WiFi switches, VPN reconnects, and laptop sleep/wake:

mosh --ssh='ssh -p 2222' hatchpod@localhost

Tailscale VPN (Optional)

Connect from anywhere without exposing ports publicly. Set TS_AUTHKEY in your .env:

  1. Generate an auth key at Tailscale Admin → Settings → Keys
  2. Add to .env:
    TS_AUTHKEY=tskey-auth-xxxxx
    
  3. Restart: make down && make up
  4. Connect via your Tailscale IP:
    ssh -p 2222 hatchpod@<tailscale-ip>

Networking mode: The container auto-detects TUN device availability at startup:

  • Kernel TUN mode (default with docker-compose.yml): Transparent routing — all apps can reach Tailscale peers without any proxy configuration. Requires cap_add: NET_ADMIN and /dev/net/tun device (both provided in docker-compose.yml).
  • Userspace fallback (no TUN device): Apps must use the SOCKS5 proxy at localhost:1055 explicitly. A TAILSCALE_PROXY variable is written to /etc/profile.d/tailscale-proxy.sh for convenience, but is not exported to avoid breaking general internet connectivity.

Configuration

Environment Variables

Variable Description Default
SSH_PASSWORD SSH password for hatchpod user changeme
API_PASSWORD API server + Web UI password changeme
TS_AUTHKEY Tailscale auth key (enables VPN) (disabled)
TS_HOSTNAME Tailscale node name hatchpod
DOTFILES_REPO Git URL for dotfiles repo (disabled)
DOTFILES_BRANCH Branch to checkout (default)

Authentication

Hatchpod uses the interactive login flow. Run claude inside the container and follow the login link. Credentials are stored in ~/.claude/ which is backed by the home Docker volume, so they persist across restarts.

MCP Servers

MCP servers configured inside the container persist across restarts:

ssh -p 2222 hatchpod@localhost
claude mcp add my-server -- npx some-mcp-server

Dotfiles (Optional)

Set DOTFILES_REPO in your .env to automatically clone and install dotfiles on first boot:

DOTFILES_REPO=https://github.com/youruser/dotfiles.git

On first boot, the repo is cloned to ~/dotfiles. If an install script (install.sh, setup.sh, or bootstrap.sh) is found, it runs automatically. Otherwise, if a Makefile is present, make is run.

Volumes

Volume Container Path Purpose
home /home/hatchpod Claude config, workspace, dotfiles, npm globals
docker-data /var/lib/docker Docker images, containers, layers

Docker-in-Docker

Hatchpod includes Docker Engine inside the container. With Sysbox installed on the host, agents can build and run Docker containers securely without --privileged.

# Verify DinD works
make docker-test

# Use Docker inside the container
make shell
docker run --rm alpine echo "Hello from nested container"
docker build -t myapp .

The docker-data volume persists pulled images and build cache across container restarts.

Architecture

┌──────────────────────────────────────────────────────────┐
│            hatchpod container (sysbox-runc)               │
│                                                          │
│  ┌────────┐  ┌─────────┐  ┌──────┐  ┌──────────┐        │
│  │  api   │  │  sshd   │  │dockerd│  │tailscaled│        │
│  │ :8080  │  │  :2222  │  │ DinD  │  │(opt-in)  │        │
│  └────┬───┘  └────┬────┘  └───┬──┘  └────┬─────┘        │
│       │           │           │           │              │
│  ┌────┴───────────┴───────────┴───────────┘              │
│  │  Provider Abstraction Layer (NormalizedMessage)        │
│  │  └─ ClaudeAdapter → Claude Code CLI                   │
│  └───────────────────────────────────────────────────────┘│
│       Node.js 20 · Python 3 · uv/uvx (MCP)              │
│                                                          │
│  Volumes:                                                │
│   /home/hatchpod   → home vol                            │
│   /var/lib/docker  → docker-data vol                     │
└──────────────────────────────────────────────────────────┘

Process supervision by s6-overlay.

Make Targets

Target Description
make build Build the Docker image
make up Start the container
make down Stop the container
make logs Follow container logs
make shell Open a shell in the container
make ssh SSH into the container
make mosh Connect via mosh
make clean Stop container, remove volumes and image
make docker-test Run hello-world inside the container (DinD smoke test)

Upgrading from claude-box

If you're upgrading from an earlier version named "claude-box", there are three breaking changes:

1. Volume name changed (claude-homehome). Migrate your data before starting:

# Stop the old container
docker compose down

# Create the new volume and copy data
docker volume create hatchpod_home
docker run --rm \
  -v claude-box_claude-home:/from \
  -v hatchpod_home:/to \
  alpine sh -c "cp -a /from/. /to/"

2. Linux user changed (claudehatchpod). The migration above copies the files, but internal paths shift from /home/claude/ to /home/hatchpod/. The container's init script automatically fixes ownership on boot.

3. Env var renamed (CLAUDE_USER_PASSWORDSSH_PASSWORD). Update your .env file. The old name still works temporarily but prints a deprecation warning.

Security Notes

  • Change all default passwords in .env before exposing to a network
  • The .env file is excluded from git via .gitignore
  • SSH root login is disabled
  • For remote access, use SSH tunneling or put behind a reverse proxy with TLS
  • The hatchpod user has passwordless sudo inside the container

Backup and Restore

# Backup
docker run --rm -v hatchpod_home:/data -v $(pwd):/backup alpine \
    tar czf /backup/home-backup.tar.gz -C /data .

# Restore
docker run --rm -v hatchpod_home:/data -v $(pwd):/backup alpine \
    tar xzf /backup/home-backup.tar.gz -C /data

Built with s6-overlay · Sysbox

About

A persistent, self-hosted Claude Code environment you can access from any machine — via SSH, Mosh, web terminal, or Tailscale VPN.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors