A persistent, self-hosted remote development environment that provides browser-based VS Code, managed dev server routing, and secure remote access on hardware you own.
- Name: Homeport
- Domain/GitHub: TBD (gethomeport.com / homeportdev.com available)
- Server: Dell OptiPlex 3070 Micro (i5-9500T 6-core, 16GB RAM, SSD)
- OS: Ubuntu (to be installed)
- Role: Alongside other Docker services (not dedicated)
- Daemon: Go (fast, single binary, great for system-level work)
- UI: Minimal, responsive, Cmd+K command palette, light mode only
- State: SQLite
- Deployment: Docker Compose
- Proxy: Caddy
- Primary: Cloudflare Tunnel (path-per-port:
dev.domain.com/3000) - Fallback: Tailscale for when CF is down
- SSL: Cloudflare terminates SSL, internal traffic is HTTP
- Auth: Cloudflare Access as base layer, optional password per port
- Auth:
gh auth login(GitHub CLI) - Cloning: On-demand via UI (paste URL or pick from list)
- Sync: Pull on demand through UI
- Git flow: Claude Code commits and pushes directly
- Management: Homeport installs, configures, and supervises it
- Instances: One shared instance (switch folders as needed)
- Access: Opens directly to selected repo
Repo-first, not port-first.
Homeport Dashboard
βββ manifold (running: 8000, 3000)
β βββ :8000 - backend [private]
β βββ :3000 - frontend [shared, expires 24h]
βββ client-website (stopped)
βββ my-nextjs-app (running: 5173)
βββ :5173 - dev server [private]
Every dev server belongs to a repo. No orphan ports.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INTERNET β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cloudflare Tunnel β
β (dev.yourdomain.com) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Caddy β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β / β Homeport UI (control panel) β β
β β /code β code-server β β
β β /3000 β localhost:3000 (via homeportd) β β
β β /5173 β localhost:5173 (via homeportd) β β
β β /api β homeportd API β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Homeport UI β β code-server β β homeportd β
β (static SPA) β β (VS Code) β β (daemon) β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββββββββββββββ
β /srv/homeport β
β βββ repos/ β
β β βββ manifold/ β
β β βββ my-nextjs-app/ β
β βββ data/ β
β β βββ homeport.db (SQLite) β
β β βββ config.yaml β
β βββ code-server/ β
β βββ (settings, extensions) β
βββββββββββββββββββββββββββββββββββββββ
The core daemon that manages everything.
Responsibilities:
- Port scanning (3000-9999) to auto-detect dev servers
- Process supervision for dev servers started via UI
- Sharing policy enforcement (private/password/public)
- State persistence (repos, ports, sharing config)
- CLI socket for
homeportcommands - REST API for UI and menu bar client
API Endpoints:
GET /api/repos - List repos
POST /api/repos - Add repo (clone from GitHub)
DELETE /api/repos/:id - Remove repo
GET /api/repos/:id/servers - List dev servers for repo
POST /api/repos/:id/start - Start dev server (detect from package.json)
POST /api/repos/:id/stop - Stop dev server
GET /api/ports - List detected ports
POST /api/ports/:port/share - Set sharing mode
GET /api/ports/:port/logs - Access logs for shared port
GET /api/status - System status
POST /api/proxy/:port/* - Proxy request to localhost:port
Port Scanning:
- Scan every 5 seconds
- Range: 3000-9999
- Method:
ssor/proc/net/tcp - Associate ports with repos by checking CWD of process
Sharing Modes:
type ShareMode struct {
Mode string // "private" | "password" | "public"
Password string // bcrypt hash (if mode=password)
ExpiresAt *time.Time // nil = never
AccessLog []AccessEntry
}State Persistence:
- SQLite database at
/srv/homeport/data/homeport.db - Restore running servers on reboot
- Configurable idle timeout to stop servers
Installed on the server, usable by Claude Code and via SSH.
# Repo management
homeport repos # List repos
homeport clone <github-url> # Clone a repo
homeport open <repo> # Open repo in code-server (returns URL)
# Server management
homeport start <repo> # Start dev server (detects from package.json)
homeport stop <repo> # Stop dev server
homeport restart <repo> # Restart dev server
homeport list # List all running servers
# Sharing
homeport share <port> # Make port accessible (private by default)
homeport share <port> --password # Password-protect (prompts for password)
homeport share <port> --public # Make fully public
homeport share <port> --expires 24h # Set expiration
homeport unshare <port> # Remove sharing
homeport url <port> # Get shareable URL
# Info
homeport status # Overall status
homeport logs <port> # Show access logs for portMinimal, clean, light-mode SPA.
Views:
- Dashboard - List of repos with running/stopped status + system stats (CPU/RAM/disk)
- Repo Detail - Dev servers for this repo, start/stop, sharing controls
- Share Modal - Set mode, password, expiration
- Settings - Port ranges, timeouts, GitHub connection
- Logs - Access logs for shared ports
Features:
- Responsive (works on iPad and phone)
- Cmd+K command palette
- Real-time updates (WebSocket)
- One-click open code-server for any repo
- Quick share with link copy
Tech:
- Vanilla JS or Preact (keep it minimal)
- Tailwind CSS
- Built into single static bundle
Browser-based VS Code.
Configuration:
- Managed by homeportd
- Extensions: Claude Code pre-installed
- Auth: Handled by Cloudflare Access (code-server auth disabled)
- Workspace: Opens to
/srv/homeport/repos
Routes:
dev.yourdomain.com/ β Homeport UI
dev.yourdomain.com/code/* β code-server
dev.yourdomain.com/api/* β homeportd API
dev.yourdomain.com/:port/* β homeportd proxy β localhost:port
Dynamic routing:
- Caddy config is static
- Port routing goes through homeportd which handles auth and proxies
- WebSocket connections proxied for hot reload (Vite HMR, Next.js Fast Refresh)
Tunnel:
- Single tunnel to Caddy
- Ingress rule:
dev.yourdomain.comβlocalhost:80
Access Policy:
- Application:
dev.yourdomain.com/* - Policy: Email = your-email@domain.com (or one-time PIN)
- Bypass: None (everything behind Access)
Per-port passwords are additional:
- Cloudflare Access gets you to the proxy
- homeportd enforces port-level passwords
-- Repos
CREATE TABLE repos (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
path TEXT NOT NULL,
github_url TEXT,
start_command TEXT, -- Auto-detected or manual override
auto_start BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
-- Detected/registered ports
CREATE TABLE ports (
port INTEGER PRIMARY KEY,
repo_id TEXT REFERENCES repos(id),
pid INTEGER,
share_mode TEXT DEFAULT 'private', -- private, password, public
password_hash TEXT,
expires_at TIMESTAMP,
first_seen TIMESTAMP,
last_seen TIMESTAMP
);
-- Access logs
CREATE TABLE access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
port INTEGER,
ip TEXT,
user_agent TEXT,
timestamp TIMESTAMP,
authenticated BOOLEAN
);
-- Server state (for restore on reboot)
CREATE TABLE server_state (
repo_id TEXT PRIMARY KEY,
was_running BOOLEAN,
ports TEXT, -- JSON array of ports
stopped_at TIMESTAMP
);/srv/homeport/
βββ repos/ # Cloned repositories
β βββ manifold/
β βββ my-nextjs-app/
βββ data/
β βββ homeport.db # SQLite database
β βββ config.yaml # Configuration
βββ code-server/
βββ config.yaml # code-server config
βββ extensions/ # Installed extensions
~/Desktop/ClaudeFolder/homeport/ # Source code
βββ cmd/
β βββ homeportd/ # Daemon entrypoint
β βββ homeport/ # CLI entrypoint
βββ internal/
β βββ daemon/ # Core daemon logic
β βββ port/ # Port scanning
β βββ proxy/ # HTTP proxy
β βββ repo/ # Repo management
β βββ share/ # Sharing logic
β βββ store/ # SQLite storage
βββ ui/ # Web UI source
β βββ index.html
β βββ app.js
β βββ style.css
βββ docker/
β βββ Dockerfile
β βββ docker-compose.yml
β βββ Caddyfile
βββ scripts/
β βββ install.sh # CLI installer
βββ go.mod
βββ go.sum
βββ ARCHITECTURE.md
βββ README.md
# 1. Clone and run installer
git clone https://github.com/gethomeport/homeport
cd homeport
./scripts/install.sh
# 2. Installer prompts:
# - Cloudflare Tunnel setup (browser auth)
# - Domain to use
# - GitHub auth (runs `gh auth login`)
# - Port range (default 3000-9999)
# 3. Starts Docker Compose
docker compose up -d
# 4. Access at https://dev.yourdomain.comMust have for v0.1:
- homeportd daemon with port scanning
- Basic web UI showing repos and running servers
- code-server integration (open repo in browser VS Code)
- Cloudflare Tunnel setup
- Share port with optional password
- CLI:
homeport list,homeport share,homeport url
Deferred:
- Auto-detect start command from package.json
- UI "Start" button (v0.1 relies on manual
npm run dev) - Access logs UI
- Cmd+K command palette
- Tailscale fallback
- Reboot state restoration
- Menu bar macOS client
- No inbound ports - All access via Cloudflare Tunnel
- Cloudflare Access - Identity-based auth for everything
- Password hashing - bcrypt for port passwords
- Port range restriction - Only scan/proxy configured ranges
- No privilege escalation - Daemon runs as non-root
- Secrets in .env - Never in code or database
- Port scan: < 100ms
- Proxy latency: < 10ms added
- UI load: < 500ms
- Dev server access: No perceptible delay vs localhost
Phase 2: Temporary Instances
- One-click WordPress, Ghost, etc.
- Docker-based app templates
- Isolated environments per instance
Phase 3: macOS Menu Bar Client
- Swift/SwiftUI native app
- List repos and running servers
- Start/stop/share controls
- One-click open URLs
- Auth via API token
- Hot reload: Yes, must work. Proxy WebSocket connections for Vite/Next.js HMR.
- Process management: Let servers die when terminal closes. No supervision needed. Homeport just detects what's running.
- URL structure: Paths only (dev.domain.com/3000). Simpler setup, one wildcard cert.
- System stats: Yes, show CPU/RAM/disk on dashboard.
Homeport supports one-click self-upgrades from the UI.
Publishing (maintainer):
- Commit and push changes to GitHub
- Create a release:
gh release create v1.2.0 --title "v1.2.0" --notes "What's new" - GitHub Actions automatically builds and pushes Docker image to
ghcr.io/ryanmish/homeport:v1.2.0
Upgrading (user):
- Dashboard checks for updates on page load
- Blue notification dot appears on Settings button when update available
- Open Settings β Updates section shows version comparison and release notes
- Click "Upgrade" β Downloading (~30s) β Restarting (~5s) β Verifying β Done
- Page auto-reloads with new version
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User clicks "Upgrade" in Settings β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β POST /api/upgrade β
β Backend triggers docker/upgrade.sh β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β upgrade.sh (runs detached, survives container restart) β
β 1. Pre-flight: Check disk space (need 1GB free) β
β 2. docker pull ghcr.io/ryanmish/homeport:v1.2.0 (retry 3x) β
β 3. Tag current image as :rollback β
β 4. docker compose up -d (restart with new image) β
β 5. Health check: curl /api/status (30s timeout) β
β 6. Write status to /srv/homeport/data/upgrade-status.json β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend polls GET /api/upgrade/status β
β Shows progress: Downloading β Restarting β Verifying β Done β
β Auto-reloads page on completion β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| File | Purpose |
|---|---|
.github/workflows/release.yml |
Builds and pushes Docker image on GitHub release |
docker/upgrade.sh |
Upgrade script with retries, health check, rollback tagging |
internal/api/upgrade.go |
API handlers for /api/upgrade and /api/upgrade/status |
internal/version/version.go |
Version checking against GitHub releases |
Version is injected at build time via Go ldflags:
ARG VERSION=dev
RUN go build -ldflags="-s -w -X .../version.Version=${VERSION}" -o homeportdGitHub Actions passes VERSION=${{ github.ref_name }} (e.g., v1.2.0).
| Scenario | Handling |
|---|---|
| Network error during pull | Retry up to 3 times with 5s delay |
| Disk space insufficient | Pre-flight check blocks upgrade |
| Concurrent upgrade attempts | Lock file prevents race condition |
| New container crashes | Health check detects, reports error |
| User closes browser | Upgrade continues, reload shows result |
If an upgrade fails, the previous image is tagged as :rollback:
docker tag ghcr.io/ryanmish/homeport:rollback ghcr.io/ryanmish/homeport:latest
cd ~/homeport/docker && docker compose up -dGET /api/updates - Check for updates (compares with GitHub releases)
POST /api/upgrade - Start upgrade (body: { "version": "v1.2.0" })
GET /api/upgrade/status - Poll upgrade progress
Update response:
{
"current_version": "1.1.0",
"latest_version": "1.2.0",
"update_available": true,
"release_notes": "## What's New\n- Feature X\n- Bug fix Y",
"release_url": "https://github.com/ryanmish/homeport/releases/tag/v1.2.0"
}Status response:
{
"step": "pulling",
"message": "Downloading update...",
"error": false,
"completed": false,
"version": "1.2.0"
}