Module 4: Deployment Automation
Project: Complete Deployment Workflow
Project: Complete Deployment Workflow
Project overview
This project integrates everything learned in Module 4: automatic changelog, readiness validation, staging → production flow with approval gates. You'll build a working end-to-end deployment workflow, from the merge to main to the deploy to production, with notifications to the team at each stage.
It's not a theoretical exercise. The deliverable is a repository with:
- A working pipeline (tested at least on mock staging)
- Operational documentation for the team
- A cost and time analysis
- An extension plan for your real case
By completing it, you'll have a deployment template you can adapt to any professional project you touch.
Project Goal
Build a GitHub Actions workflow that runs the complete deployment cycle with Claude Code assisting at each critical step.
By completing it:
- ✅ A pipeline executable from merge to main all the way to production
- ✅ A changelog generated automatically with each release
- ✅ Readiness validation before each deploy
- ✅ Automatic deploy to staging with smoke tests
- ✅ A human approval gate before production
- ✅ A pre-promotion analysis that informs the reviewer
- ✅ Post-deploy notifications to the team
- ✅ Complete operational documentation
Technical Specifications
Project structure
deployment-pipeline-project/
├── .github/
│ └── workflows/
│ ├── release-changelog.yml # capsule 02
│ ├── readiness-validation.yml # capsule 03
│ └── staged-deployment.yml # capsule 04 + integration
├── scripts/
│ ├── generate_changelog.py # capsule 02
│ ├── validate_readiness.py # capsule 03
│ ├── validate_staging.py # capsule 04
│ ├── deploy.sh # mock deploy script
│ ├── smoke_tests.sh # mock smoke tests
│ ├── post_deploy_notification.py # new
│ └── platform_helpers.py # common helpers
├── docs/
│ ├── DEPLOYMENT.md # how the pipeline operates
│ ├── RUNBOOK.md # what to do if X fails
│ └── METRICS.md # cost analysis
├── CLAUDE.md
├── CHANGELOG.md
└── README.md
Initial setup
mkdir deployment-pipeline-project && cd deployment-pipeline-project
git init
git checkout -b main
# Configure GitHub
gh repo create my-deployment-project --public
# Configure environments in GitHub Settings:
# - staging (no required reviewers)
# - production (with required reviewers + branch protection)
The Integrated Workflow
# .github/workflows/staged-deployment.yml
name: Staged Deployment
on:
push:
branches: [main]
permissions:
contents: read
pull-requests: write
deployments: write
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
env:
PYTHON_VERSION: '3.11'
jobs:
# ============================================
# JOB 1: Readiness Validation
# ============================================
validate:
runs-on: ubuntu-latest
outputs:
readiness_status: ${{ steps.validate.outputs.status }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- run: pip install anthropic requests
- name: Run readiness validation
id: validate
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
python scripts/validate_readiness.py
if [ $? -eq 0 ]; then
echo "status=ready" >> $GITHUB_OUTPUT
else
echo "status=blocked" >> $GITHUB_OUTPUT
exit 1
fi
- uses: actions/upload-artifact@v4
if: always()
with:
name: readiness-report
path: readiness_report.json
# ============================================
# JOB 2: Generate Changelog
# ============================================
generate-changelog:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- run: pip install anthropic requests
- name: Generate changelog from last release
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
NEW_TAG: ${{ github.sha }}
run: python scripts/generate_changelog.py
- uses: actions/upload-artifact@v4
with:
name: changelog
path: CHANGELOG_*.md
# ============================================
# JOB 3: Deploy to Staging
# ============================================
deploy-staging:
needs: [validate, generate-changelog]
runs-on: ubuntu-latest
environment: staging
outputs:
deploy_url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
id: deploy
env:
DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
run: |
./scripts/deploy.sh staging
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
- name: Smoke tests
run: ./scripts/smoke_tests.sh https://staging.example.com
- name: Validate staging metrics with Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: python scripts/validate_staging.py
# ============================================
# JOB 4: Deploy to Production (with approval gate)
# ============================================
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # ← required reviewers configured
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
run: ./scripts/deploy.sh production
- name: Smoke tests on production
run: ./scripts/smoke_tests.sh https://app.example.com
- name: Notify team
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: python scripts/post_deploy_notification.py
Mock Script: deploy.sh
#!/bin/bash
# scripts/deploy.sh — mock deploy for testing the pipeline
set -euo pipefail
ENV="${1:?'Usage: deploy.sh <staging|production>'}"
echo "[deploy.sh] Deploying to $ENV..."
echo "[deploy.sh] Pulling latest image..."
sleep 2
echo "[deploy.sh] Running migrations..."
sleep 1
echo "[deploy.sh] Updating containers..."
sleep 2
echo "[deploy.sh] Health check..."
sleep 1
echo "[deploy.sh] Deploy to $ENV complete."
# In real production, this script would invoke:
# kubectl rollout restart deployment/app -n $ENV
# or
# terraform apply -auto-approve -var "env=$ENV"
# or your platform's specific mechanism
Make the script executable: chmod +x scripts/deploy.sh
Mock Script: smoke_tests.sh
#!/bin/bash
# scripts/smoke_tests.sh — mock smoke tests
set -euo pipefail
URL="${1:?'Usage: smoke_tests.sh <url>'}"
echo "[smoke] Testing $URL..."
# In real production, run real requests:
# curl -f $URL/health || exit 1
# curl -f $URL/api/status || exit 1
# Mock: simulate tests
PASSED=0
FAILED=0
SKIPPED=0
for test in health api status auth payments; do
sleep 0.5
if [ $((RANDOM % 10)) -lt 9 ]; then
echo " ✓ $test"
PASSED=$((PASSED + 1))
else
echo " ✗ $test (FAILED)"
FAILED=$((FAILED + 1))
fi
done
echo "{\"passed\": $PASSED, \"failed\": $FAILED, \"skipped\": $SKIPPED}" > smoke_results.json
if [ "$FAILED" -gt 0 ]; then
echo "[smoke] $FAILED tests failed"
exit 1
fi
echo "[smoke] All $PASSED tests passed"
New Script: post_deploy_notification.py
"""scripts/post_deploy_notification.py
Notifies the team of the successful deploy with a useful summary.
"""
import json
import os
import sys
from pathlib import Path
import requests
from anthropic import Anthropic
def get_changelog() -> str:
"""Read the last generated changelog."""
changelogs = sorted(Path(".").glob("CHANGELOG_*.md"))
if changelogs:
return changelogs[-1].read_text()
return "No changelog available"
def generate_notification_summary(changelog: str) -> str:
"""Generate an executive summary for Slack/email."""
prompt = f"""Generate a brief message (maximum 150 words) to notify the
team of a successful deploy to production. The message must:
1. Start with "🚀 Deployed to production"
2. Summarize the changelog's main changes in 2-3 bullets
3. Mention whether there are breaking changes
4. Close with "Monitoring in progress. Check [dashboard link]."
CHANGELOG:
{changelog[:3000]}
Output: only the message, with no additional text.
"""
client = Anthropic()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=500,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text.strip()
def send_slack_notification(message: str, webhook: str):
"""Send to Slack via a webhook."""
payload = {
"text": message,
"username": "Deploy Bot",
"icon_emoji": ":rocket:",
}
r = requests.post(webhook, json=payload, timeout=10)
if r.status_code != 200:
print(f"WARNING: Slack notification failed: {r.status_code}", file=sys.stderr)
def main() -> int:
changelog = get_changelog()
summary = generate_notification_summary(changelog)
print("--- Notification preview ---")
print(summary)
print("---")
webhook = os.environ.get("SLACK_WEBHOOK")
if webhook:
send_slack_notification(summary, webhook)
print("Notification sent to Slack")
else:
print("SLACK_WEBHOOK not configured, skipping notification")
return 0
if __name__ == "__main__":
sys.exit(main())
Documentation: docs/DEPLOYMENT.md
# Deployment Pipeline
This document describes the project's deployment flow.
## When it triggers
- **Trigger:** push to `main` (typically via a PR merge)
- **Branches:** only `main` deploys. Other branches only run CI.
## Stages
### 1. Validate (~30s)
- Tests pass
- Linting + type checking
- Readiness check (env vars, migrations, deps)
- Analysis with Claude Code to detect issues
### 2. Generate Changelog (~30s)
- Lists commits since the last deploy
- Generates a structured changelog
- Uploads as an artifact
### 3. Deploy Staging (~3-5 min)
- Automatic deploy
- Smoke tests
- Metrics validation with Claude Code
### 4. Approval Gate (variable)
- Human reviewer evaluates the deploy
- Has info pre-loaded (Job 3's analysis)
- Click "Approve" or investigate
### 5. Deploy Production (~3-5 min)
- Deploy
- Smoke tests
- Notification to the team
## Expected total time
- Without issues: 8-15 minutes
- With human investigation: variable
## Who approves
Reviewers configured in the `production` environment:
- @tech-lead
- @sre-lead
Anyone can approve.
## When NOT to deploy
- Weekends (no SRE on-call)
- Before critical business events
- When staging shows persistent issues
Documentation: docs/RUNBOOK.md
# Runbook: What to Do When X Fails
## Validate Failure
**Symptoms:** The `validate` job fails with a readiness check error.
**Action:**
1. Download the `readiness-report.json` artifact
2. Identify the check that failed
3. If it's a missing env var in production: add it in Settings → Environments → production
4. If it's a migration: verify it's applied and then re-trigger
## Staging Deploy Failure
**Symptoms:** The `deploy-staging` job fails.
**Action:**
1. Look at the job logs
2. If it's an infrastructure error: re-run the job
3. If it's an application error: revert the commit on `main`, investigate offline
4. Do NOT promote to production while staging fails
## Smoke Tests Fail on Staging
**Symptoms:** Smoke tests report failures.
**Action:**
1. Verify the URL/endpoints are accessible
2. If a specific endpoint fails: review the change that affects that endpoint
3. If EVERYTHING fails: probable infrastructure issue
4. Do NOT approve production
## Approval Gate "Stuck"
**Symptoms:** The production job has been waiting a long time for approval.
**Action:**
1. Verify the reviewers are notified
2. If urgent: contact a reviewer directly
3. If nobody is available: revert the merge and retry
## Production Deploy Failure
**Symptoms:** Production fails post-deploy.
**Action:**
1. **Immediate:** trigger a rollback (manual or automatic depending on the setup)
2. Verify metrics in the monitoring dashboard
3. Postmortem
4. Do NOT re-deploy the same change without investigation
Analysis: docs/METRICS.md
# Pipeline Metrics
Pipeline data after [N runs].
## Average times
| Stage | Average | P95 |
|-------|----------|-----|
| Validate | 35s | 45s |
| Generate changelog | 25s | 40s |
| Deploy staging | 4 min | 6 min |
| Smoke tests | 90s | 2 min |
| Approval wait | 12 min | 1 hour |
| Deploy production | 4 min | 6 min |
| **Total (without wait)** | **11 min** | **17 min** |
## Estimated costs
Per run:
- Anthropic API: ~$0.05 (several calls to Claude)
- GitHub Actions runner: ~$0.04 (5 min × $0.008)
- **Total: ~$0.09 per run**
Monthly (assuming 50 deploys/month):
- ~$4.50/month in CI/CD
## Frequency
- Average deploys/week: 12
- Manual steps before vs now:
- Before: 8 (manual changelog, manual readiness, communication, etc.)
- Now: 1 (approval click)
## Time saved
- Per deploy: ~25 min of human time
- Monthly: ~25 min × 50 deploys = ~20 hours
ROI: ~$2K/month saved (with a developer cost of ~$100/h)
Project Deliverables
- A working repository with:
- A workflow running end-to-end (at least staging)
- Scripts (Python + bash)
- Environment configuration
- Documentation:
docs/DEPLOYMENT.mddocs/RUNBOOK.mddocs/METRICS.md
- A demonstration:
- Screenshot/video of the pipeline running
- The complete pipeline (all jobs green)
- The approval gate working
- An extension plan documented:
- How to apply it to your real project
- Which mock scripts to replace with real ones
- What specific adaptations it needs
Evaluation Rubric (100 points)
Working Pipeline (40 pts)
- ✅ (10 pts) Validate runs and blocks correctly
- ✅ (5 pts) The changelog is generated automatically
- ✅ (10 pts) Deploy to staging is completely automatic
- ✅ (10 pts) The approval gate before production works
- ✅ (5 pts) The post-deploy notification is sent
Code Quality (20 pts)
- ✅ (8 pts) Python scripts with error handling
- ✅ (4 pts) Appropriate exit codes
- ✅ (4 pts) Clear logs for debugging
- ✅ (4 pts) Clean structure (config + main + helpers)
Documentation (25 pts)
- ✅ (10 pts) DEPLOYMENT.md explains the complete flow
- ✅ (10 pts) RUNBOOK.md covers the main failure modes
- ✅ (5 pts) METRICS.md has quantitative analysis
Professional Setup (10 pts)
- ✅ (5 pts) Environments configured correctly (staging vs production)
- ✅ (5 pts) Secrets separated per environment
Reflection and Plan (5 pts)
- ✅ (3 pts) A plan to adapt it to a real project
- ✅ (2 pts) Lessons learned documented
Extra Credit (+15 pts)
- ✅ (+5 pts) Implement an additional canary deploy
- ✅ (+3 pts) Integration with a real observability system (Datadog, Prometheus, etc.)
- ✅ (+3 pts) Test the rollback (simulate a post-deploy failure)
- ✅ (+2 pts) Multi-region deploy (us-east + eu-west, etc.)
- ✅ (+2 pts) Interactive Slack bot (react with an emoji to approve)
Common Errors in the Project
1. Not testing the complete flow
Symptom: You implemented each job but never ran the pipeline end-to-end.
Why it happens: The separate jobs seem to work.
How to fix it: Make a test PR, merge it, watch the pipeline run completely. Any inconsistency comes out here.
2. Approval gate without an assigned reviewer
Symptom: The deploy-production job stays "waiting for approval" but nobody is configured.
Why it happens: You forgot to add required reviewers to the production environment.
How to fix it: Settings → Environments → production → Required reviewers. Add yourself for testing.
3. Non-executable scripts
Symptom: ./scripts/deploy.sh: Permission denied.
Why it happens: Bash scripts need the +x flag.
How to fix it:
chmod +x scripts/*.sh
git update-index --chmod=+x scripts/*.sh
git commit -am "fix: make scripts executable"
4. Superficial documentation
Symptom: RUNBOOK.md has "what to do if it fails: investigate".
Why it happens: Documentation written quickly without thinking about real cases.
How to fix it: For each failure mode, write concrete steps: exact command, where to look at logs, who to contact. The runbook must be usable under pressure.
5. Ignoring costs in METRICS
Symptom: The project is complete but you didn't analyze how much it costs.
Why it happens: Costs seem secondary when something works.
How to fix it: Calculate cost per run, monthly, ROI vs the manual process. It's what justifies the pipeline to the team.
Final Reflection
When you finish this project, you have a professional template of deployment automation that:
- Applies the 6 modules of the path (CI/CD, code review, SDK, scripts, secrets, observability)
- Is portfolio-worthy — it demonstrates senior-level thinking about deployment
- Is transferable — the pattern applies to any project
The most important thing: you learned to balance automation with human judgment. Automation eliminates repetitive work. The human adds context and authority. That balance is the difference between a pipeline the team trusts and one the team disables.
Connection with Module 5
The next module adds post-deploy resilience: what happens when production fails? Automatic rollback, security scanning as an additional gate, smart diagnosis of incidents. Your current pipeline deploys correctly; Module 5 makes it resistant to failures.
Resources for the Project
- GitHub Actions Workflow Examples — Official templates
- The DevOps Handbook — A classic reference
- Kubernetes Deployment Strategies — If you use K8s
- Slack Incoming Webhooks — For notifications
- Datadog GitHub Integration — Modern observability
- Anthropic API Best Practices — Applicable to deployment