# Vps Deploy

> |

- **Type:** Skill
- **Install:** `agentstack add skill-byerlikaya-claude-starter-kit-vps-deploy`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [byerlikaya](https://agentstack.voostack.com/s/byerlikaya)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [byerlikaya](https://github.com/byerlikaya)
- **Source:** https://github.com/byerlikaya/claude-starter-kit/tree/main/claude-starter/skills/vps-deploy
- **Website:** https://www.npmjs.com/package/@byerlikaya/claude-starter-kit

## Install

```sh
agentstack add skill-byerlikaya-claude-starter-kit-vps-deploy
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# VPS Deploy

A solid deploy has a single idea: **swap the running version with the new one in a reversible way.**
That is, when you put the new version in place, don't throw the old one away — keep it aside. If the new version
can't pass the health gate, undo the swap and no one notices. This skill builds the deploy around this swap;
Docker or bare-metal, a single app or different runtimes, it makes no difference.

> **Kit adaptation (local, .claude/):** Default backend is **.NET (Docker recommended)**. **Deploy requires explicit
> approval (§4.4)**; a backup before every swap and a health gate after are mandatory. If `.deploy.yml` carries server
> credentials it goes into `.gitignore`. §4 Prohibitions apply.

## Checklist
- [ ] Runtime + deploy method determined
- [ ] Config taken from `.deploy.yml`/the user, SSH verified
- [ ] Reverse proxy + (if there is a domain) SSL in place
- [ ] Running version backed up to `releases/`
- [ ] User approved the deploy, new version deployed
- [ ] Health gate passed (otherwise automatic rollback performed)
- [ ] If requested, single-command scripts (`deploy.sh` / `adopt.sh`) generated

---

## Phase 1 — Surface: method and runtime

```bash
if   [ -f Dockerfile ] || [ -f docker-compose.yml ]; then METHOD=docker
elif [ -f package.json ];                                then METHOD=bare RUNTIME=node
elif [ -f requirements.txt ] || [ -f pyproject.toml ];   then METHOD=bare RUNTIME=python
elif [ -f go.mod ];                                      then METHOD=bare RUNTIME=go
else METHOD=unknown; fi
```
If `method: docker` and there's no Dockerfile, generate one. `method: bare` → a process manager suited to the runtime. If detection is ambiguous, ask the user. **.NET:** Docker recommended; if bare is required, run the `dotnet publish -c Release` output with systemd.

---

## Phase 2 — Preparation: config, SSH, proxy, SSL

**Config** — read `.deploy.yml` from the root, and if it's missing ask for each field:
```yaml
host: 192.168.1.100        # VPS IP/hostname (required)
user: deploy               # SSH user (required)
ssh_key: ~/.ssh/id_rsa     # private key
app_port: 3000             # application port (required)
health_check: /api/health  # HTTP path (default /)
deploy_path: /var/www/app  # server path (required)
method: auto               # auto | docker | bare
domain: app.example.com    # for proxy/SSL (optional)
ssl: true                  # default true if there is a domain
reverse_proxy: auto        # auto | nginx | caddy
```
**Verify SSH first** — if you can't connect, stop immediately:
```bash
ssh -i $SSH_KEY -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new $USER@$HOST "echo OK"
```

**Reverse proxy** — detect the one installed on the server (`auto` → Caddy if present, otherwise Nginx). The application binds only to `127.0.0.1`; it is exposed outward **only** through the proxy.

Nginx site config

```nginx
server {
  listen 80;
  server_name DOMAIN;
  location / {
    proxy_pass http://127.0.0.1:APP_PORT;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
```
Link it into `sites-enabled` with `ln -s`, then `nginx -t && systemctl reload nginx`.

Caddyfile (SSL included, automatic)

```
DOMAIN {
  reverse_proxy 127.0.0.1:APP_PORT
}
```
`systemctl reload caddy`. Caddy obtains/renews the certificate on its own — no extra step.

**SSL (Nginx path)** — skip if `ssl: false` or there's no domain; don't do SSL on a bare IP. First verify that DNS resolves to the host (`dig +short $DOMAIN` = `$HOST`), then Certbot:
```bash
certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m admin@$DOMAIN
systemctl enable certbot.timer   # automatic renewal
```

---

## Phase 3 — Swap: keep the old, put the new

**First back up the running version** (timestamped `releases/`, last 3 kept):
```bash
ssh -i $SSH_KEY $USER@$HOST "
  mkdir -p $DEPLOY_PATH/releases
  [ -d $DEPLOY_PATH/current ] && cp -a $DEPLOY_PATH/current $DEPLOY_PATH/releases/$(date +%Y%m%d_%H%M%S)
  cd $DEPLOY_PATH/releases && ls -dt */ | tail -n +4 | xargs -r rm -rf
"
```

**Docker** — build the image locally, transfer it, replace the container:
```bash
docker build -t $APP:latest .
docker save $APP:latest | gzip | ssh -i $SSH_KEY $USER@$HOST "gunzip | docker load"
ssh -i $SSH_KEY $USER@$HOST "
  docker rm -f $APP 2>/dev/null || true
  docker run -d --name $APP --restart unless-stopped -p 127.0.0.1:$APP_PORT:$APP_PORT $APP:latest
"
```

**Bare-metal** — `rsync` the source, install dependencies, start with the process manager:
```bash
rsync -avz --delete --exclude .git --exclude node_modules --exclude .venv \
  -e "ssh -i $SSH_KEY" ./ $USER@$HOST:$DEPLOY_PATH/current/
```

| Runtime | Dependencies + startup |
|---|---|
| Node | `npm ci --production` → PM2: `pm2 start ecosystem.config.js --name $APP || pm2 start npm --name $APP -- start` → `pm2 save` |
| Python | `python3 -m venv venv && venv/bin/pip install -r requirements.txt` → systemd (gunicorn/uvicorn or `python main.py`) |
| Go | build locally `GOOS=linux GOARCH=amd64 go build -o $APP` → `scp` → systemd |

systemd unit (for Python/Go, fill in ExecStart per the runtime):
```ini
[Unit]
After=network.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$DEPLOY_PATH/current
ExecStart=
Restart=always
Environment=PORT=$APP_PORT
[Install]
WantedBy=multi-user.target
```
`systemctl daemon-reload && systemctl enable --now $APP`.

---

## Phase 4 — Health gate

Right after the swap; retry for ~30 s until a 200 comes back. **If it doesn't pass, trigger Phase 5 automatically.**
```bash
ssh -i $SSH_KEY $USER@$HOST '
  for i in $(seq 1 6); do
    [ "$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:'"$APP_PORT$HEALTH_CHECK"')" = 200 ] \
      && { echo "Health gate PASSED"; exit 0; }
    sleep 5
  done
  echo "Health gate FAILED"; exit 1
'
```
Also verify the process is up: `docker ps` on Docker, otherwise `systemctl is-active $APP` / `pm2 show $APP`.

---

## Phase 5 — Rollback

Undo the swap: put the most recent version in `releases/` back into `current`, restart the service, repeat the health gate (Phase 4).
```bash
LATEST=$(ssh -i $SSH_KEY $USER@$HOST "ls -dt $DEPLOY_PATH/releases/*/ 2>/dev/null | head -1")
[ -z "$LATEST" ] && { echo "ERROR: no backup — rollback impossible"; exit 1; }
ssh -i $SSH_KEY $USER@$HOST "
  mkdir -p $DEPLOY_PATH/current
  rsync -a --delete ${LATEST%/}/ $DEPLOY_PATH/current/   # atomic swap — no 'rm -rf' (guard-safe)
  { docker rm -f $APP 2>/dev/null && docker run -d --name $APP --restart unless-stopped \
      -p 127.0.0.1:$APP_PORT:$APP_PORT $APP:previous; } \
    || systemctl restart $APP || pm2 restart $APP
"
```
> **Note:** Rollback is done with `rsync --delete` instead of `rm -rf`; that way `guard-bash` (the local `rm -rf` block)
> doesn't block the automatic rollback. The deploy verbs (`ssh`/`rsync`/`docker`) pass through the approval gate via
> `settings.json` `permissions.ask` — they run with approval, not silently.

---

## Single-command scripts (optional)

After a successful deploy, **ask:** "Shall I generate `deploy.sh` and `adopt.sh` so you can deploy/update with a single command in the future?"

If you generate them, **don't use a generic template** — analyze the actual project (package.json scripts / Dockerfile / Makefile / docker-compose / `.env.example`) and derive the exact build+start steps. Two scripts:
- **`deploy.sh`** — first-time setup + deploy: load config · verify SSH · proxy + SSL · back up to `releases/` · project-specific build · transfer · dependencies · start · health gate with retries · rollback on failure.
- **`adopt.sh`** — quick update: config · backup · code sync · (if needed) build/dependencies · restart · health gate · rollback on failure.

Rules: `chmod +x`; if `.deploy.yml` carries credentials, ask about `.gitignore`; don't overwrite an existing script without approval (show a diff); no hardcoded credentials — always read from `.deploy.yml`; comment and explain every project-specific decision.

---

## Invariant rules
1. **No deploy without approval** — show the method/host/domain/port plan, wait for approval.
2. **Always back up before the swap** — timestamped into `releases/`; if the backup fails, abort.
3. **Always a health gate after the swap** — HTTP + process; if it doesn't pass, automatic rollback.
4. **Keep the last 3 versions** — don't delete them all.
5. **Don't deploy to prod without knowing the target** — host/deploy_path/domain must be approved.
6. **Verify SSH first** — if you can't connect, fail fast.
7. **Don't expose the port directly** — the app binds to `127.0.0.1`, outward only through the proxy.
8. **SSL is mandatory when there's a domain** — always set it up unless `ssl` is explicitly `false`.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [byerlikaya](https://github.com/byerlikaya)
- **Source:** [byerlikaya/claude-starter-kit](https://github.com/byerlikaya/claude-starter-kit)
- **License:** MIT
- **Homepage:** https://www.npmjs.com/package/@byerlikaya/claude-starter-kit

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-byerlikaya-claude-starter-kit-vps-deploy
- Seller: https://agentstack.voostack.com/s/byerlikaya
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
