skills/deploy-prod/SKILL.md
Generates a GitHub Actions production deploy pipeline via SSH with sequential jobs. Reads docker-compose-prod.yml and .env.prod.example to auto-detect services, secrets, and network configuration.
npx skillsauth add landim32/awesome-ai-skills deploy-prodInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert DevOps assistant that generates GitHub Actions production deployment pipelines. The pipeline deploys via SSH using password authentication and is split into the maximum number of sequential jobs.
The user may provide additional context or customization: $ARGUMENTS
If no arguments are provided, analyze the current project and generate the pipeline.
All SSH steps use appleboy/ssh-action@v1 with password authentication:
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
# commands here
Required GitHub Secrets (connection):
| Secret | Description |
|--------|-------------|
| PROD_SSH_HOST | Production server IP/hostname |
| PROD_SSH_USER | SSH username |
| PROD_SSH_PASSWORD | SSH password |
| PROD_SSH_PORT | SSH port (default: 22) |
The pipeline MUST be split into the maximum number of sequential jobs. Each job has a single, clear responsibility. Jobs run in strict sequence using needs:.
checkout → inject-secrets → network-setup → stop-services → build-deploy → health-check → summary
Every SSH job MUST begin with set -e and define DEPLOY_DIR consistently.
checkout — Clone or Update RepositoryClones the repository on first deploy, or fetches and resets to the latest commit on subsequent deploys.
checkout:
runs-on: ubuntu-latest
steps:
- name: Clone or update repository
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
DEPLOY_DIR="/opt/$PROJECT_NAME"
REPO_URL="https://github.com/${{ github.repository }}.git"
BRANCH="main"
if [ -d "$DEPLOY_DIR" ]; then
echo "Updating existing repository..."
cd "$DEPLOY_DIR"
git fetch origin
git reset --hard "origin/$BRANCH"
git clean -fd
else
echo "Cloning repository..."
git clone --branch "$BRANCH" --single-branch "$REPO_URL" "$DEPLOY_DIR"
fi
echo "Repository ready at $DEPLOY_DIR"
inject-secrets — Write .env.prod FileWrites the .env.prod file with secrets from GitHub Secrets. The variables are discovered by reading .env.prod.example.
inject-secrets:
runs-on: ubuntu-latest
needs: checkout
steps:
- name: Inject secrets into .env.prod
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
DEPLOY_DIR="/opt/$PROJECT_NAME"
cd "$DEPLOY_DIR"
cat > .env.prod <<'ENVEOF'
$SECRET_VARIABLES_HERE
ENVEOF
sed -i 's/^[[:space:]]*//' .env.prod
echo ".env.prod written successfully"
network-setup — Ensure Docker Network ExistsCreates the external Docker network if it doesn't already exist. The network name is discovered from docker-compose-prod.yml.
network-setup:
runs-on: ubuntu-latest
needs: inject-secrets
steps:
- name: Ensure Docker network exists
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
docker network inspect $EXTERNAL_NETWORK >/dev/null 2>&1 || docker network create $EXTERNAL_NETWORK
echo "Network $EXTERNAL_NETWORK ready"
stop-services — Stop Running ContainersGracefully stops existing containers before rebuilding.
stop-services:
runs-on: ubuntu-latest
needs: network-setup
steps:
- name: Stop running containers
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
DEPLOY_DIR="/opt/$PROJECT_NAME"
cd "$DEPLOY_DIR"
docker compose --env-file .env.prod -f docker-compose-prod.yml down || true
echo "Services stopped"
build-deploy — Build and Start ServicesBuilds Docker images and starts all services in detached mode.
build-deploy:
runs-on: ubuntu-latest
needs: stop-services
steps:
- name: Build and start services
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
DEPLOY_DIR="/opt/$PROJECT_NAME"
cd "$DEPLOY_DIR"
docker compose --env-file .env.prod -f docker-compose-prod.yml up --build -d
echo "Services started"
health-check — Verify DeploymentWaits for services to start and verifies they are running correctly.
health-check:
runs-on: ubuntu-latest
needs: build-deploy
steps:
- name: Verify deployment health
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.PROD_SSH_HOST }}
username: ${{ secrets.PROD_SSH_USER }}
password: ${{ secrets.PROD_SSH_PASSWORD }}
port: ${{ secrets.PROD_SSH_PORT || 22 }}
script: |
set -e
DEPLOY_DIR="/opt/$PROJECT_NAME"
cd "$DEPLOY_DIR"
echo "Waiting for services to start..."
sleep 10
docker compose --env-file .env.prod -f docker-compose-prod.yml ps
# Health check via HTTP
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$APP_PORT/ || echo "000")
if [ "$HTTP_STATUS" = "200" ]; then
echo "Health check passed (HTTP $HTTP_STATUS)"
else
echo "WARNING: Health check returned HTTP $HTTP_STATUS"
docker compose --env-file .env.prod -f docker-compose-prod.yml logs --tail=50
exit 1
fi
summary — Write Deployment SummaryWrites a summary to the GitHub Actions job summary. This is the only job that does NOT use SSH.
summary:
runs-on: ubuntu-latest
needs: health-check
steps:
- name: Deployment summary
run: |
echo "## ✅ Production Deployment" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Server | \`${{ secrets.PROD_SSH_HOST }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`main\` |" >> $GITHUB_STEP_SUMMARY
echo "| Commit | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Triggered by | \`${{ github.actor }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Timestamp | \`$(date -u +'%Y-%m-%d %H:%M:%S UTC')\` |" >> $GITHUB_STEP_SUMMARY
Before generating the pipeline, read these files to auto-detect configuration:
docker-compose-prod.yml — Discover:
network-setup job)health-check job).env.prod.example — Discover:
${{ secrets.VAR_NAME }}inject-secrets jobDockerfile or docker-compose-prod.yml build: section — Discover:
Repository name — From git remote -v or infer from directory name:
DEPLOY_DIR (/opt/<project-name-lowercase>)When generating the pipeline:
$PROJECT_NAME: Replace with the actual project name (lowercase, kebab-case). Used for DEPLOY_DIR=/opt/$PROJECT_NAME.
$EXTERNAL_NETWORK: Read from docker-compose-prod.yml → networks: section → find the one with external: true.
$SECRET_VARIABLES_HERE: Read .env.prod.example, extract each variable name, and map to GitHub Secrets syntax:
VAR_NAME=${{ secrets.VAR_NAME }}
$APP_PORT: Read from docker-compose-prod.yml → ports: → extract the host port (left side of :).
Workflow trigger: Default is workflow_dispatch (manual). If the user requests automatic triggers, add push: with branch filters.
Save the generated pipeline to .github/workflows/deploy-prod.yml.
After generating, report:
needs: pointing to the previous jobappleboy/ssh-action@v1 with password: fieldset -e — every SSH script MUST start with set -e for fail-fast behaviorDEPLOY_DIR — must be consistent across all jobsdocker-compose-prod.yml, never hardcode|| true on stop — the stop-services job should not fail if containers aren't running yet (first deploy)tools
Guides how to integrate the zTools package for ChatGPT, DALL-E image generation, file upload (S3), slug generation, email sending, and document validation in a .NET 8 project. Use when the user wants to use AI features, upload files, generate slugs, send emails, or understand zTools integration.
documentation
Generates a comprehensive, standardized README.md for any project. Use when the user wants to create or regenerate a README file following the project's documentation standard.
development
Create modal dialogs in the frontend using a custom Modal component built on top of Radix UI Dialog. Use this skill whenever the user asks to create, add, or modify a modal, dialog, popup, or confirmation prompt in the React application.
development
Create the complete frontend architecture for a new entity in the React application. Generates TypeScript types, service class, context provider, custom hook, and registers the provider in main.tsx. Use this skill when the user asks to create a new entity, feature module, or domain area in the frontend.