Skip to content
Development
Command

/cg-indie-deploy

Deploy an application to a single VPS with reverse proxy, automatic TLS, systemd process supervision, firewall, and basic health checks. Use when the user mentions deploying to a server, VPS setup, going to production, Hetzner/DigitalOcean/Vultr/Linode deployment, setting up

From plugin
claude-ground
1168 skills8 commands

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/cg-indie-deploy

Context preview

What this command does when you run it.

Deploy an application to a single VPS with reverse proxy, automatic TLS, systemd process supervision, firewall, and basic health checks. Use when the user mentions deploying to a server, VPS setup, going to production, Hetzner/DigitalOcean/Vultr/Linode deployment, setting up

Command definition

cg-indie-deploy.md
name: cg-indie-deploy
description: Deploy an application to a single VPS with reverse proxy, automatic TLS, systemd process supervision, firewall, and basic health checks. Use when the user mentions deploying to a server, VPS setup, going to production, Hetzner/DigitalOcean/Vultr/Linode deployment, setting up Caddy or nginx, writing a systemd service, or any "how do I put this app online" question. NOT for Kubernetes, managed cloud services (AWS ECS, Cloud Run, App Engine), or serverless platforms.

cg-indie-deploy

Deploy an application to one VPS with production-grade defaults. Targets indie developers and small teams shipping to a single server — not enterprise infrastructure, not container orchestration, not managed cloud services.

Scope

**This skill covers:**

  • Initial server hardening (SSH, firewall, deploy user)
  • Reverse proxy setup with automatic TLS (Caddy or nginx)
  • Process supervision with systemd
  • Application deployment for binaries, runtime apps, and Docker Compose
  • Health checks and log rotation
  • Update/rollback workflow for subsequent deploys
  • Basic backup strategy

**This skill does NOT cover:**

  • Kubernetes, Docker Swarm, Nomad, or any orchestrator
  • Managed services (AWS ECS, GCP Cloud Run, Azure App Service, Fly.io, Railway, Render)
  • Serverless (Lambda, Cloudflare Workers, Vercel Functions)
  • Multi-server / load-balanced deployments
  • CI/CD pipeline creation (separate concern — this skill is the deploy target)
  • DNS registrar configuration (too provider-specific)

If the user needs any of the excluded items, say so and suggest a better-matching tool.

Required context — gather before generating anything

MUST have answers to these before producing commands. Ask the user directly if unclear; do not guess.

1. **Application type** — determines artifact and service config:

  • `binary` — Go, Rust, compiled languages. Copy binary, run as systemd service.
  • `node` — Node.js, Bun, Deno. Install runtime, run via systemd.
  • `python` — Python with venv. Install deps, run via systemd (or gunicorn/uvicorn for web).
  • `docker` — Docker Compose on the VPS, no systemd needed for the app itself.
  • `static` — Static HTML/JS/CSS. Reverse proxy serves files directly, no process.

2. **VPS provider** — affects only initial setup commands and DNS instructions. Core workflow is identical across: Hetzner, DigitalOcean, Linode/Akamai, Vultr, OVH, or any generic Ubuntu/Debian host.

3. **Reverse proxy preference** — default to Caddy unless user asks otherwise:

  • **Caddy** (RECOMMENDED) — automatic TLS via Let's Encrypt, minimal config, sane defaults. Less to go wrong.
  • **nginx** — if user already has it, prefers it, or needs features Caddy doesn't have.
  • **Traefik** — only if the setup is Docker-heavy and the user asks for it.

4. **Domain state** — MUST verify before attempting TLS:

  • Domain pointed to the server IP via an A record?
  • Root domain (`example.com`) or subdomain (`app.example.com`)?
  • If DNS is not configured yet, stop and tell the user to do that first. Do not attempt TLS before DNS propagates.

5. **Listening port** — what port does the app bind to locally? Default by type if user doesn't know: Go 8080, Node 3000, Python 8000. This is internal only; the reverse proxy handles 80/443.

Workflow

Execute phases in order. Do not skip. Each phase has verification — do not proceed if verification fails.

Phase 1 — Server hardening (fresh server only)

Skip if the server already has a non-root `deploy` user, UFW enabled, and root SSH disabled.

# Update
apt update && apt upgrade -y
apt install -y ufw curl ca-certificates

# Deploy user
adduser --disabled-password --gecos "" deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

# Firewall — only SSH, HTTP, HTTPS
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

# Disable root SSH and password auth
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh

# fail2ban — reduces brute-force noise, blocks repeat offenders
apt install -y fail2ban
systemctl enable fail2ban
systemctl start fail2ban
# Default config bans IPs after 5 failed SSH attempts for 10 minutes. Good enough.

# Unattended security upgrades — auto-install security patches only
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades  # answer Yes
# Verify: cat /etc/apt/apt.conf.d/20auto-upgrades should show "1" for both lines
# This only installs security patches, not all upgrades. Low risk, high value.

**CRITICAL**: Before disabling root login, verify the deploy user can SSH in from a separate terminal. Never close the only working session until you have tested the replacement.

**Optional — Tailscale for SSH**: If you want to eliminate public SSH exposure entirely, install [Tailscale](https://tailscale.com/) on the server and your local machine. Then change UFW to only allow SSH from the Tailscale interface:

ufw delete allow OpenSSH
ufw allow in on tailscale0 to any port 22

This is the single strongest SSH hardening measure — no public SSH port means no brute-force attacks, no fail2ban needed, no port scanning. Free for personal use.

**Verification**: `ssh deploy@server` works, `ufw status` shows active, `curl https://example.com` from outside reaches the server (or times out because nothing is listening yet — that's fine, firewall is not blocking).

Phase 2 — Install reverse proxy

**Caddy** (default):

apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith
Read more
Ships withclaude-ground

A minimal rule system for Claude Code. Gives Claude the structural discipline it lacks by default — phase tracking, decision logging, honest pushback, debug discipline, language-specific best practices, and reusable skills.

Get the whole plugin

Other commands on claude-ground.