Deploys features to staging environment with database migrations, health checks, and deployment verification...
Example workflow:
Verifying PR merged... ā
Merge commit: abc123f "Merge pull request #47"
Checking for migrations...
Found: migrations/2025_11_add_user_profile.sql
Running migrations... ā
Triggering staging deployment...
Platform: Vercel/Netlify/Railway
Deployment ID: dpl_abc123
Status: Building... ā Deploying... ā Live ā
Running health checks...
API: https://staging.example.com/health ā 200 OK ā
Database: Connected ā
Cache: Operational ā
Staging deployment successful!
Ship report: specs/NNN-slug/staging-ship-report.md
Staging URL: https://staging.example.com
/ship-staging command executedPrerequisites:
Confirm pull request successfully merged before deploying.
Validation:
# Check recent commits for merge commit
git log --oneline -10 | grep "Merge pull request"
# Verify feature branch merged
FEATURE_SLUG="user-profile-editing"
git log --oneline --all | grep -i "$FEATURE_SLUG"
# Confirm on main branch
CURRENT_BRANCH=$(git branch --show-current)
if [ "$CURRENT_BRANCH" != "main" ]; then
echo "ā Not on main branch (currently on $CURRENT_BRANCH)"
echo "Switch to main: git checkout main && git pull"
exit 1
fi
echo "ā
PR merged to main"
Blocking conditions:
Output:
ā
PR merged to main
Merge commit: abc123f "Merge pull request #47: Add user profile editing"
Author: @username
Merged: 2 minutes ago
Execute database migrations if schema changes exist.
Detection:
# Check for migration files (Node.js example)
if [ -d "migrations" ] && [ -n "$(ls -A migrations/*.sql 2>/dev/null)" ]; then
echo "ā
Migrations detected"
# List migrations
ls -1 migrations/*.sql
# Run migrations
npm run migrate:staging
# Or with migration tool
# knex migrate:latest --env staging
# prisma migrate deploy --schema=./schema.prisma
# Verify migrations applied
echo "ā
Migrations completed"
else
echo "ā¹ļø No migrations to run"
fi
Error handling:
# If migration fails
if [ $? -ne 0 ]; then
echo "ā Migration failed"
echo "Review migration logs for errors"
echo "DO NOT proceed with deployment"
echo ""
echo "Common issues:"
echo " - Syntax errors in SQL"
echo " - Constraint violations"
echo " - Missing columns in dependent tables"
exit 1
fi
Rollback procedure:
# If deployment fails after migration
# Rollback migrations
npm run migrate:rollback:staging
# Or manually revert last migration
# psql -h staging-db.example.com -d mydb -f migrations/rollback/2025_11_revert.sql
Deploy to staging environment based on deployment platform.
Platform-specific commands:
Vercel:
# Deploy to staging (preview environment)
vercel --yes
# Or trigger via git push
git push origin main
# Vercel auto-deploys main ā staging
Netlify:
# Deploy to staging environment
netlify deploy --prod=false
# Or via git push to staging branch
git push origin main:staging
Railway:
# Trigger deployment
railway up --environment staging
GitHub Actions:
# Trigger workflow
gh workflow run deploy-staging.yml
# Monitor workflow
gh run watch
Docker/Custom:
# Build and deploy
docker build -t myapp:staging .
docker push registry.example.com/myapp:staging
# SSH to staging server
ssh staging.example.com
docker pull registry.example.com/myapp:staging
docker-compose up -d
Monitor deployment:
# Get deployment ID/URL
DEPLOYMENT_ID="dpl_abc123xyz"
STAGING_URL="https://staging.example.com"
echo "Deployment ID: $DEPLOYMENT_ID"
echo "Staging URL: $STAGING_URL"
echo ""
echo "Monitoring deployment..."
# Platform-specific monitoring commands
Verify all services and endpoints operational after deployment.
Critical checks:
echo "Running health checks..."
# Check 1: API health endpoint
API_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$STAGING_URL/health")
if [ "$API_STATUS" = "200" ]; then
echo " ā
API: $API_STATUS OK"
else
echo " ā API: $API_STATUS FAILED"
HEALTH_FAILED=true
fi
# Check 2: Database connectivity
DB_STATUS=$(curl -s "$STAGING_URL/health" | jq -r '.database')
if [ "$DB_STATUS" = "connected" ]; then
echo " ā
Database: Connected"
else
echo " ā Database: $DB_STATUS"
HEALTH_FAILED=true
fi
# Check 3: Cache/Redis
CACHE_STATUS=$(curl -s "$STAGING_URL/health" | jq -r '.cache')
if [ "$CACHE_STATUS" = "operational" ]; then
echo " ā
Cache: Operational"
else
echo " ā Cache: $CACHE_STATUS"
HEALTH_FAILED=true
fi
# Check 4: Version matches
DEPLOYED_VERSION=$(curl -s "$STAGING_URL/api/version" | jq -r '.version')
EXPECTED_VERSION=$(node -p "require('./package.json').version")
if [ "$DEPLOYED_VERSION" = "$EXPECTED_VERSION" ]; then
echo " ā
Version: $DEPLOYED_VERSION"
else
echo " ā ļø Version mismatch: deployed=$DEPLOYED_VERSION, expected=$EXPECTED_VERSION"
fi
# Fail if any health check failed
if [ "$HEALTH_FAILED" = "true" ]; then
echo ""
echo "ā Health checks failed - deployment may be broken"
echo "Review logs and consider rollback"
exit 1
fi
echo ""
echo "ā
All health checks passed"
Health check endpoints:
/health - Overall system health/api/version - Deployed version/api/status - Detailed service status/api/db/ping - Database connectivityCreate staging-ship-report.md with deployment metadata.
Report generation:
FEATURE_SLUG="user-profile-editing"
REPORT_PATH="specs/001-$FEATURE_SLUG/staging-ship-report.md"
cat > "$REPORT_PATH" <<EOF
# Staging Ship Report: $FEATURE_SLUG
## Deployment Summary
- **Deployed at**: $(date -Iseconds)
- **Deployment ID**: $DEPLOYMENT_ID
- **Staging URL**: $STAGING_URL
- **Branch**: main
- **Commit**: $(git rev-parse HEAD)
- **Author**: $(git log -1 --format='%an <%ae>')
## Health Checks
- API endpoints: ā
All passing
- Database: ā
Connected
- Cache: ā
Operational
- Version: ā
$DEPLOYED_VERSION
## Database Migrations
$(if [ -d "migrations" ]; then
echo "- Migrations run: $(ls migrations/*.sql | wc -l) files"
ls -1 migrations/*.sql | sed 's/^/ - /'
else
echo "- No migrations required"
fi)
## Next Steps
- Manual validation: Test feature on staging environment
- Run: /validate-staging to approve for production
- Staging URL: $STAGING_URL
## Rollback Procedure
1. Revert deployment: [platform-specific command]
2. Rollback migrations (if any): npm run migrate:rollback:staging
3. Verify rollback successful
---
Generated by /ship-staging on $(date)
EOF
echo "ā
Ship report generated: $REPORT_PATH"
Update workflow state:
# Update state.yaml
STATE_FILE="specs/001-$FEATURE_SLUG/state.yaml"
# Add deployment metadata
yq eval ".deployment.staging.url = \"$STAGING_URL\"" -i "$STATE_FILE"
yq eval ".deployment.staging.deployment_id = \"$DEPLOYMENT_ID\"" -i "$STATE_FILE"
yq eval ".deployment.staging.deployed_at = \"$(date -Iseconds)\"" -i "$STATE_FILE"
yq eval ".current_phase = \"validate-staging\"" -i "$STATE_FILE"
echo "ā
Workflow state updated"
Show deployment results and next steps.
Summary format:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Staging Deployment Successful!
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Staging URL: https://staging.example.com
Deployment ID: dpl_abc123xyz
Deployed at: 2025-11-19T22:45:00Z
Health Checks: ā
All passing
- API: 200 OK
- Database: Connected
- Cache: Operational
- Version: 2.7.0
Ship Report: specs/001-user-profile-editing/staging-ship-report.md
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Next: Manual Validation
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
1. Test feature on staging: https://staging.example.com
2. Verify all acceptance criteria met
3. Run: /validate-staging to approve for production
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
curl -f https://staging.example.com/health || { echo "ā Health check failed - deployment broken" exit 1 }
**Impact**: Broken staging deployment goes unnoticed, blocks validation
**Prevention**: Always run automated health checks, verify 200 status and service health
</pitfall>
<pitfall name="deploy_on_failing_ci">
**ā Deploying when CI is failing on main**
```bash
# BAD: CI shows red, deploy anyway
# "Tests failing but it's probably fine..."
/ship-staging
ā Always wait for CI to pass before deploying
# GOOD: Check CI status first
gh run list --branch main --limit 1 --json conclusion --jq '.[0].conclusion'
# If "success", proceed with deployment
# If "failure", fix CI first
Impact: Deploying broken code to staging, wasting time debugging obvious failures Prevention: Make CI passing a prerequisite gate for staging deployment
**Impact**: Unable to recover quickly from broken deployment, extended downtime
**Prevention**: Document and test rollback procedure, include in ship report
</pitfall>
<pitfall name="missing_deployment_metadata">
**ā Not recording deployment details**
```bash
# BAD: Deploy but don't save deployment ID, URL, timestamp
vercel --yes
# Later: "What version is on staging? When was it deployed?"
ā Always generate ship report with metadata
# GOOD: Capture and save deployment info
DEPLOYMENT_ID=$(vercel --yes | grep -oP 'https://[^ ]+')
echo "Deployment ID: $DEPLOYMENT_ID" >> staging-ship-report.md
echo "Deployed at: $(date -Iseconds)" >> staging-ship-report.md
Impact: No audit trail, can't correlate staging issues with deployments Prevention: Always generate staging-ship-report.md with full metadata
Quality gates passed: