deploy ยท diff
git:20260222.eea05d3 to git:20260222.a812b0b
7 added, 16 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; }
+ Run these checks as separate Bash commands:
- # Dockerfile exists
- DOCKERFILE="${DEPLOY_DOCKERFILE:-./Dockerfile}"
- test -f "$DOCKERFILE" || { echo "ERROR: Dockerfile not found at $DOCKERFILE"; exit 1; }
- ```
+ 1. Verify `DEPLOY_HOST` is set: `printenv DEPLOY_HOST` (must produce output)
+ 2. Verify `DEPLOY_IMAGE` is set: `printenv DEPLOY_IMAGE` (must produce output)
+ 3. Verify Docker daemon: `docker info > /dev/null 2>&1`
+ 4. Verify SSH connectivity: `ssh -o ConnectTimeout=5 -o BatchMode=yes <deploy-host> "echo OK"` (replace `<deploy-host>` with the actual DEPLOY_HOST value)
+ 5. Verify Dockerfile exists: `test -f ./Dockerfile` (or the DEPLOY_DOCKERFILE path if set)
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-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
+ bash ./plugins/soleur/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 <container-name> --tail 50"
```
**If `DEPLOY_HEALTH_URL` is not set:**
Report deployment as complete without health verification:
```text
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.