deploy ยท diff
git:20260213.c66bdcc to git:20260222.eea05d3
9 added, 7 removed. Audit B to B.
---
name: deploy
description: "This skill should be used when deploying containerized applications to remote servers. It builds Docker images, pushes to GHCR, and deploys via SSH with health verification. Triggers on \"deploy\", \"deploy to production\", \"push to server\", \"ship to remote\"."
---
# Deploy Skill
Deploy a containerized application to a remote server via Docker and SSH.
## Environment Variables
| Variable | Required | Description | Example |
|----------|----------|-------------|---------|
| `DEPLOY_HOST` | Yes | SSH target (user@host) | `root@203.0.113.10` |
| `DEPLOY_IMAGE` | Yes | GHCR image path | `ghcr.io/org/app` |
| `DEPLOY_HEALTH_URL` | No | Health check endpoint | `http://203.0.113.10:3000/health` |
| `DEPLOY_CONTAINER` | No | Container name (default: image basename) | `myapp` |
| `DEPLOY_DOCKERFILE` | No | Dockerfile path (default: `./Dockerfile`) | `./docker/Dockerfile.prod` |
For first-time server setup, see [hetzner-setup.md](./references/hetzner-setup.md).
## Phase 0: Validate
Check prerequisites before starting the build.
```bash
# Required env vars
echo "${DEPLOY_HOST:?Set DEPLOY_HOST env var}" > /dev/null
echo "${DEPLOY_IMAGE:?Set DEPLOY_IMAGE env var}" > /dev/null
# Docker daemon
docker info > /dev/null 2>&1 || { echo "ERROR: Docker daemon not running"; exit 1; }
# SSH connectivity
ssh -o ConnectTimeout=5 -o BatchMode=yes "$DEPLOY_HOST" "echo OK" > /dev/null 2>&1 \
|| { echo "ERROR: SSH to $DEPLOY_HOST failed"; exit 1; }
# Dockerfile exists
DOCKERFILE="${DEPLOY_DOCKERFILE:-./Dockerfile}"
test -f "$DOCKERFILE" || { echo "ERROR: Dockerfile not found at $DOCKERFILE"; exit 1; }
```
If any check fails, stop and report the error. Do not proceed to Phase 1.
## Phase 1: Plan
Show the deployment summary and ask for confirmation.
Use the **AskUserQuestion tool** to present:
**Question:** "Deploy this configuration?"
**Options:**
1. **Deploy** -- Proceed with build and deployment
2. **Cancel** -- Abort deployment
Display before asking:
```text
Deployment Plan
- Image: $DEPLOY_IMAGE:$(git rev-parse --short HEAD)
- Target: $DEPLOY_HOST
- Container: ${DEPLOY_CONTAINER:-$(basename $DEPLOY_IMAGE)}
- Dockerfile: ${DEPLOY_DOCKERFILE:-./Dockerfile}
- Health URL: ${DEPLOY_HEALTH_URL:-none}
+ Image: <DEPLOY_IMAGE>:<git-short-sha>
+ Target: <DEPLOY_HOST>
+ Container: <DEPLOY_CONTAINER or image basename>
+ Dockerfile: <DEPLOY_DOCKERFILE or ./Dockerfile>
+ Health URL: <DEPLOY_HEALTH_URL or none>
```
+ Resolve `<git-short-sha>` by running `git rev-parse --short HEAD`.
+
If the user selects Cancel, stop execution.
## Phase 2: Execute
Run [deploy.sh](./scripts/deploy.sh) to build, push, and deploy:
```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/deploy/scripts/deploy.sh
```
The script handles:
1. Build Docker image tagged with git SHA and `:latest`
2. Push both tags to GHCR
3. SSH to remote host: pull image, stop old container, start new container
## Phase 3: Verify
After deployment completes, verify the service is running.
**If `DEPLOY_HEALTH_URL` is set:**
The deploy script runs a health check (5 attempts, 3s apart).
If the health check fails:
```text
Health check failed. Debug with:
- ssh $DEPLOY_HOST "docker logs ${DEPLOY_CONTAINER:-$(basename $DEPLOY_IMAGE)} --tail 50"
+ ssh <DEPLOY_HOST> "docker logs <container-name> --tail 50"
```
**If `DEPLOY_HEALTH_URL` is not set:**
Report deployment as complete without health verification:
```text
- Deployed $(git rev-parse --short HEAD) to $DEPLOY_HOST
+ Deployed <git-short-sha> to <DEPLOY_HOST>
No health URL configured -- skipping verification.
```
## Important Rules
- Never hardcode secrets in the deploy script. Use environment variables.
- Never deploy without user confirmation (Phase 1 gate).
- The script uses `set -euo pipefail` -- any command failure aborts the deployment.
- Container restart uses stop+pull+start, not `docker restart`, to ensure the new image is used.