Your own Heroku, running on a Mac. A lightweight platform for deploying containerized apps with a familiar CLI experience — homelab deploy and you're live.
homelab deploy myapp --create # First deploy: creates app + deploys
homelab logs -t # Tail logs (auto-detects app from git remote)
homelab config:set KEY=value # Set environment variablesBuilt on Dokku, secured with Tailscale, with optional network-wide ad blocking via Pi-hole.
- Heroku-like CLI -
homelab deploy,homelab logs,homelab config— muscle memory transfers - Git push to deploy - Push code, get a running app at
myapp.homelab.yourdomain.com - Auto-detection - CLI detects app name from git remote, no need to specify it every time
- Private by default - Everything accessible only via Tailscale VPN
- Custom domains - Wildcard DNS for automatic subdomain routing
- AI-friendly setup - Self-documenting API that Claude (or any agent) can follow
- Network-wide ad blocking - Pi-hole for all devices on your network
┌─────────────────────────────────────────────────────────────────┐
│ Your Mac (Homelab Server) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Colima (Docker VM) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ Pi-hole │ │ Dokku │ │ │
│ │ │ DNS + Web │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │
│ │ │ │ │ │ app │ │ app │ │ api │ │ │ │
│ │ └─────────────┘ │ └─────┘ └─────┘ └─────┘ │ │ │
│ │ │ ▲ │ │ │
│ └───────────────────────┴─────────────│───────────────┘ │ │
│ │ │
│ ┌─────────┴───────┐ │
│ │ Dokku Router │ │
│ │ :80 / :443 │ │
│ └─────────────────┘ │
│ ▲ │
└─────────────────────────────────────────│───────────────────────┘
│
┌─────────────────────┴─────────────────────┐
│ Tailscale VPN │
│ │
┌─────────┴─────────┐ ┌─────────────┴───┐
│ Your devices │ │ Dev machine │
│ (any network) │ │ git push → │
└───────────────────┘ └─────────────────┘
git clone https://github.com/mrilikecoding/homelab.git
cd homelab
cp config.example.sh config.sh
# Edit config.sh with your settings
./install.shAfter installation, configure Tailscale DNS:
- Go to https://login.tailscale.com/admin/dns
- Add Split DNS:
homelab.YOURDOMAIN→YOUR_TAILSCALE_IP - (Optional) Add your Tailscale IP as a Global Nameserver for network-wide ad blocking
Your apps will be available at *.homelab.YOURDOMAIN (e.g., myapp.homelab.nate.green).
If you have Claude on your dev machine, ask it:
"Set up my machine to deploy to my homelab. The setup API is at http://api.homelab.YOURDOMAIN"
Claude will fetch the configuration and guide you through setup.
On any machine connected to your Tailnet:
# 1. Set your server's Tailscale IP
HOMELAB_IP="your-tailscale-ip"
# 2. Trust the host key
ssh-keyscan -p 3022 $HOMELAB_IP >> ~/.ssh/known_hosts
# 3. Add SSH config (idempotent)
grep -q "Host dokku" ~/.ssh/config 2>/dev/null || cat >> ~/.ssh/config << EOF
Host dokku
HostName $HOMELAB_IP
Port 3022
User dokku
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
EOF
# 4. Add your SSH key to Dokku (replace YOUR_USER and KEYNAME)
cat ~/.ssh/id_ed25519.pub | ssh YOUR_USER@$HOMELAB_IP "/usr/local/bin/docker exec -i dokku dokku ssh-keys:add KEYNAME"
# 5. Set your domain and PATH
echo 'export DOKKU_DOMAIN=yourdomain.com' >> ~/.zshrc
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# 6. Install homelab CLI (no sudo needed)
mkdir -p ~/.local/bin
curl -fsSL -o ~/.local/bin/homelab https://raw.githubusercontent.com/mrilikecoding/homelab/main/homelab
chmod +x ~/.local/bin/homelabcd my-app
homelab deploy myapp --createThis:
- Creates the app in Dokku
- Sets domain to
myapp.homelab.YOURDOMAIN - Adds a
dokkugit remote to your repo - Pushes and builds your code
homelab deployThe CLI auto-detects the app name from your git remote, so you don't need to specify it after the first deploy.
Your app needs one of:
Dockerfile- Dokku builds and runs it- Buildpack-compatible code (Node.js package.json, Python requirements.txt, etc.)
mkdir hello && cd hello
git init
cat > server.js << 'EOF'
const http = require('http');
const port = process.env.PORT || 5000;
http.createServer((req, res) => {
res.end('Hello from Dokku!\n');
}).listen(port);
EOF
cat > Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /app
COPY server.js .
ENV PORT=5000
EXPOSE 5000
CMD ["node", "server.js"]
EOF
git add . && git commit -m "Initial commit"
homelab deploy hello --createVisit http://hello.homelab.YOURDOMAIN
The homelab includes a self-documenting API at http://api.homelab.YOURDOMAIN that provides:
| Endpoint | Description |
|---|---|
GET / |
Overview and available endpoints |
GET /setup |
Human-readable setup instructions (markdown) |
GET /setup/agent |
Machine-readable setup for AI assistants (JSON) |
GET /ssh-config |
SSH config snippet ready to append |
GET /deploy-script |
The homelab CLI script |
GET /status |
Current Dokku apps and their status |
GET /config |
Server configuration (domain, IPs, ports) |
On a new dev machine, tell Claude:
Fetch http://api.homelab.YOURDOMAIN/setup and configure this machine to deploy apps to my homelab.
Claude will:
- Fetch the setup instructions
- Create the SSH config
- Guide you through adding your SSH key
- Install the homelab CLI
- Set the required environment variables
The homelab CLI provides a Heroku-like interface for Dokku:
Usage: homelab <command> [options]
App Management:
apps List all apps
create <name> Create a new app (adds git remote)
destroy <name> Permanently delete an app
Deployment:
deploy [name] [--create] Deploy app (auto-detects from git remote)
deploy [name] -b <branch> Deploy specific branch
Runtime:
logs [name] [-t] View logs (-t to tail/follow)
ps [name] Show running processes
run <name> <cmd> Run one-off command
restart [name] Restart app
stop [name] Stop app
start [name] Start app
Configuration:
config [name] Show config vars
config:set <name> K=V Set config vars
config:unset <name> K Unset config vars
domains [name] Show domains
Utilities:
url [name] Show app URL
ssh Interactive dokku shell
dokku <cmd> Pass-through to dokku
Auto-detection: When run from a git repo with a dokku remote, commands like deploy, logs, ps, config, and url automatically detect the app name.
The homelab CLI provides Heroku-like commands for managing your apps:
homelab apps # List all apps
homelab logs myapp # View logs
homelab logs myapp -t # Tail/follow logs
homelab ps myapp # Show process status
homelab restart myapp # Restart app
homelab config myapp # Show config vars
homelab config:set myapp KEY=val # Set config var
homelab run myapp bash # Run one-off command
homelab destroy myapp # Delete app (with confirmation)When you're inside a repo with a dokku git remote, you can omit the app name:
cd my-app
homelab logs # Auto-detects app from git remote
homelab restart
homelab configFor commands not wrapped by the CLI, use pass-through:
homelab dokku nginx:show-config myapp
homelab ssh # Interactive dokku shellBy default, apps use HTTP. To enable HTTPS with valid Let's Encrypt certificates:
homelab https:setupThis interactive wizard will:
- Install certbot and your DNS provider's plugin
- Request a wildcard certificate for
*.homelab.YOURDOMAIN - Configure Dokku to use the certificate
- Set up automatic weekly renewal
The certificate setup uses DNS-01 validation, which works without exposing your homelab publicly. Supported providers:
| Provider | Plugin | API Key Location |
|---|---|---|
| Porkbun | certbot-dns-porkbun |
https://porkbun.com/account/api |
| Cloudflare | certbot-dns-cloudflare |
https://dash.cloudflare.com/profile/api-tokens |
| Route 53 | certbot-dns-route53 |
AWS IAM credentials |
| Google Cloud | certbot-dns-google |
Service account JSON |
| DigitalOcean | certbot-dns-digitalocean |
https://cloud.digitalocean.com/account/api/tokens |
| Namecheap | certbot-dns-namecheap |
https://ap.www.namecheap.com/settings/tools/apiaccess/ |
| Manual | N/A | You add TXT records manually |
homelab https:setup # Initial setup (interactive)
homelab https:status # Show certificate info
homelab https:renew # Manually renew certificatesAdd to config.sh to skip prompts:
LETSENCRYPT_EMAIL="you@example.com"
CERTBOT_DNS_PLUGIN="porkbun" # or cloudflare, route53, etc.By default, apps are only accessible via Tailscale. To make specific apps publicly accessible while keeping everything else private, use Cloudflare Tunnel.
Internet Your Tailnet (Private)
──────── ──────────────────────
myapp.yourdomain.com ──► Cloudflare ──► Tunnel ──► Dokku ──► myapp
│
✗ Cannot reach *.homelab.yourdomain.com
- Only apps you explicitly make public are accessible from the internet
*.homelab.YOURDOMAINstays completely private (Tailnet-only)- Cloudflare provides HTTPS, DDoS protection, and caching for public apps
- No ports opened on your network — the tunnel connects outbound
Your domain must use Cloudflare DNS (free plan is fine). You can keep your current registrar (Porkbun, Namecheap, etc.) — only DNS moves to Cloudflare.
- Sign up at https://dash.cloudflare.com (free)
- Click "Add a site" in Cloudflare dashboard
- Enter your domain (e.g.,
yourdomain.com) - Select the Free plan
- Cloudflare will scan and import your existing DNS records
Important: Before changing nameservers, verify Cloudflare imported all your records, especially:
- MX records (email)
- TXT records (SPF, DKIM, DMARC for email)
- Any other records you have
Add any missing records in Cloudflare before proceeding.
- Cloudflare will show you two nameservers (e.g.,
ada.ns.cloudflare.com,bob.ns.cloudflare.com) - Go to your registrar (Porkbun, Namecheap, etc.)
- Replace the existing nameservers with Cloudflare's
- Save changes
- Cloudflare will show "Pending" until nameservers propagate
- Usually takes a few minutes, can take up to 24 hours
- Once status shows "Active", you're ready
On your homelab server:
homelab tunnel:setupThis will:
- Install
cloudflaredif needed - Open a browser to authenticate with Cloudflare
- Create a tunnel named "homelab"
- Configure the tunnel as a system service
# Make an app public (creates DNS record automatically)
homelab public myapp # → https://myapp.yourdomain.com
homelab public myapp api.yourdomain.com # → custom hostname
# Apply changes
homelab tunnel:restart
# Make it private again
homelab private myapp
homelab tunnel:restart
# List all public apps
homelab public:list| Type | URL | Access |
|---|---|---|
| Private | https://myapp.homelab.yourdomain.com |
Tailnet only |
| Public | https://myapp.yourdomain.com |
Internet |
homelab tunnel:setup # Initial Cloudflare Tunnel setup
homelab tunnel:status # Show tunnel status and public apps
homelab tunnel:restart # Restart the tunnel (required after public/private changes)
homelab tunnel:logs # View tunnel logs
homelab tunnel:logs -f # Follow tunnel logsProtect your homelab from being overwhelmed by traffic spikes:
homelab cb:enable # Enable monitoring (checks every 60s)
homelab cb:status # Show current load and thresholds
homelab cb:disable # Disable monitoringIf CPU or load exceeds thresholds for 3 consecutive checks, all public apps are automatically disabled. See config.sh to adjust thresholds.
For quick, temporary sharing without Cloudflare setup:
tailscale funnel --bg 443This exposes port 443 via your-machine.tailnet-name.ts.net. Less control than Cloudflare Tunnel, but simpler for one-off sharing.
# Pi-hole
PIHOLE_PASSWORD="your-password"
PIHOLE_TIMEZONE="America/Los_Angeles"
# Dokku
DOKKU_HOSTNAME="your-machine.tailnet-name.ts.net"
DOKKU_SSH_PORT=3022
# Your domain for apps
APP_DOMAIN="yourdomain.com"| Variable | Description | Example |
|---|---|---|
DOKKU_DOMAIN |
Your app domain (required) | nate.green |
DOKKU_HOST |
SSH host alias (optional) | dokku |
| Path | Purpose |
|---|---|
~/homelab/ |
This repo |
~/homelab/dokku/certs/ |
SSL certificates for Dokku |
~/pihole/ |
Pi-hole config and data |
~/.homelab/certs/ |
Let's Encrypt certificate storage |
~/.homelab/credentials/ |
DNS provider API credentials |
~/.cloudflared/ |
Cloudflare Tunnel config |
/Library/LaunchDaemons/com.homelab.* |
Startup services |
| Path | Purpose |
|---|---|
~/.ssh/config |
SSH config with dokku host |
~/.local/bin/homelab |
Homelab CLI |
DOKKU_DOMAIN env var |
Your domain |
cd ~/homelab/dokku
docker compose pull
docker compose up -d# Startup log
cat /tmp/homelab-startup.log
# App logs
ssh dokku logs myappKey data:
~/pihole/- Pi-hole config- Dokku volume -
docker volume inspect dokku_data
# Test SSH
ssh -v dokku version
# Check Tailscale
tailscale status# Check Pi-hole
dig myapp.homelab.yourdomain.com @YOUR_TAILSCALE_IP
# Restart Pi-hole
docker restart piholeEnsure Tailscale Split DNS is configured:
- Go to https://login.tailscale.com/admin/dns
- Add Split DNS:
homelab.YOURDOMAIN→YOUR_TAILSCALE_IP
# Check app exists
ssh dokku apps:list
# Check logs
ssh dokku logs myapp./uninstall.shMIT