The Playbook unlocked traqqit.com →

Module 4 · ~15 min · the crown jewel

🚀 Deploy to your own box behind one reverse proxy

This is the part nobody hands you, and it's why the apps are live and cheap. You'll leave with a repeatable pattern that puts your whole fleet on one small server.

The whole pattern in one picture

One cheap VPS runs Docker. On it: one shared Caddy reverse proxy owns ports 80/443, terminates TLS, and auto-provisions HTTPS certificates. Each app is a tiny static file-server container on a shared Docker network — with no public ports of its own. Caddy routes each domain to the right container. DNS is one A record per app. Updates are a file copy.

Because every app is just "a container + one Caddy block + one DNS record," your tenth app deploys exactly like your first. That sameness is the whole trick — the fleet compounds instead of each app being a new adventure.

Cost reality. One VPS in the $5–7/month range hosts the entire fleet — every app on this course's proof page runs on a single box like that. Not one bill per app. No per-request serverless surprises.

1

SSH into your box and install Docker

Use the Hostinger VPS you set up in Module 0 (any provider works, but Hostinger + the MCP is what the rest of this lesson assumes). SSH in as root — ssh root@YOUR_VPS_IP — then install Docker and create one shared network every app will join:

# on your Ubuntu VPS (skip the install line if you picked the Docker template)
curl -fsSL https://get.docker.com | sh
docker network create web
mkdir -p /srv/caddy /srv/apps

💡 Don't want to leave your editor? Ask Claude to do it: "SSH into my Hostinger VPS at <IP> and install Docker." With the Hostinger MCP connected (Module 0), Claude can also check the box's status, restart it, or manage its firewall for you.

2

Run the shared Caddy (your one front door)

Create /srv/caddy/Caddyfile with just a global block to start (an email for Let's Encrypt):

# /srv/caddy/Caddyfile
{
    email you@yourdomain.com
}

Then run Caddy — the only container that binds public ports, mounting the Caddyfile and a persistent volume for certificates:

docker run -d --name caddy --restart unless-stopped \
  --network web \
  -p 80:80 -p 443:443 \
  -v /srv/caddy/Caddyfile:/etc/caddy/Caddyfile \
  -v caddy_data:/data -v caddy_config:/config \
  caddy:2

💡 The caddy_data volume is important — it's where issued certificates live. Keep it and your certs survive restarts and never re-issue needlessly.

3

Your app = a static file-server container

Drop your index.html and assets into a folder, then run a caddy:2 file-server for them. Note the read-only mount and, crucially, no -p published port — only the shared Caddy is public; your app is reachable only inside the web network.

mkdir -p /srv/apps/yourapp
# copy your static site into /srv/apps/yourapp (index.html, assets…)

docker run -d --name yourapp --restart unless-stopped \
  --network web \
  -v /srv/apps/yourapp:/usr/share/caddy:ro \
  caddy:2
4

Wire the domain — one Caddy block

Append a block for your app to /srv/caddy/Caddyfile. This is the entire routing + HTTPS config for the app:

yourapp.yourdomain.com {
    encode gzip
    reverse_proxy yourapp:80
}

Now reload Caddy without restarting it (graceful, zero downtime for your other apps). Use the stdin form — it feeds Caddy the fresh file contents directly:

docker exec -i caddy caddy reload --config - --adapter caddyfile < /srv/caddy/Caddyfile

Caddy sees the new domain and automatically requests a Let's Encrypt certificate for it. HTTPS just works.

5

Point DNS — just ask Claude

Your app needs one DNS record: an A record for yourapp → your VPS's IP. This is where the Hostinger MCP (Module 0) earns its keep. You don't open a control panel — you ask Claude, in plain English:

EXAMPLE — WHAT YOU TYPE, AND WHAT CLAUDE DOES:

Claude Code
YouAdd an A record for "yourapp" on yourdomain.com pointing to 203.0.113.10, TTL 300.
ClaudeDone ✓ — created yourapp.yourdomain.com → 203.0.113.10 (A record, TTL 300). It'll resolve in a minute or two, then Caddy grabs the HTTPS cert automatically.↳ hostinger-dns · update DNS records

That's the whole DNS step — no panel, no copy-pasting IPs into forms. It's exactly how every app in the fleet gets its domain. (Prefer clicking? You can still add the A record in Hostinger's DNS panel by hand.) Then verify:

curl -sI https://yourapp.yourdomain.com | head -1
# HTTP/2 200   ← live, with a valid cert

⚠️ The cert needs DNS to resolve first. If you just added the record, Let's Encrypt may briefly see NXDOMAIN — harmless, Caddy retries automatically once DNS propagates (usually a minute or two).

6

Updates are a copy

No rebuild, no redeploy dance. The file-server serves whatever's in the folder, live:

scp index.html root@YOUR_VPS_IP:/srv/apps/yourapp/
# refresh the page — it's already updated

Adding app #2, #3, #10

Every new app is the same three moves you just did:

  1. Run its static container on the web network (no published port).
  2. Append its Caddy block, reload.
  3. Add its DNS record.

That's it. No new infrastructure, no migration, no per-app hosting bill. This is how a fleet gets built on evenings — the deploy stopped being a decision.

Gotchas worth knowing (they'll save you an hour)

🦥 Why this beats a platform. No cold starts, no per-seat/per-request pricing, no vendor lock-in, no build minutes. A flat monthly cost you understand, and scp to ship. Boring on purpose — boring is what's still running in a year.

Your turn ✅