CI/CD Setup Guide
Complete guide for setting up automatic deployments to production using GitHub Actions.
Overview
This setup provides:
- ✅ Automatic deployments when pushing to
mainbranch - ✅ CI tests on pull requests (RSpec, RuboCop, Brakeman)
- ✅ Zero-downtime deploys with health checks
- ✅ Automatic rollback if deployment fails
- ✅ Manual deployment trigger option
Architecture
Push to main
↓
GitHub Actions
↓
SSH to server
↓
Pull code
↓
Build image
↓
Run migrations
↓
Restart services
↓
Health check
↓
Success/Rollback
Step 1: Generate SSH Key for GitHub Actions
Generate a dedicated SSH key for GitHub Actions deployments:
# On your local machine
ssh-keygen -t ed25519 -C "github-actions@bizradar" -f ~/.ssh/github_actions_deploy
# This creates:
# - ~/.ssh/github_actions_deploy (private key - for GitHub)
# - ~/.ssh/github_actions_deploy.pub (public key - for server)
Step 2: Add Public Key to Production Server
# Copy public key to server
ssh deploy@91.98.204.128 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" < ~/.ssh/github_actions_deploy.pub
# Test the key works
ssh -i ~/.ssh/github_actions_deploy deploy@91.98.204.128 "echo 'Connection successful'"
Step 3: Configure GitHub Secrets
Go to your GitHub repository: Settings → Secrets and variables → Actions → New repository secret
Add these secrets:
Required Secrets:
-
PRODUCTION_SSH_KEY
# Get the private key content
cat ~/.ssh/github_actions_deploy- Copy the ENTIRE output (including
-----BEGIN OPENSSH PRIVATE KEY-----and-----END OPENSSH PRIVATE KEY-----) - Paste into GitHub secret
- Copy the ENTIRE output (including
-
PRODUCTION_HOST
91.98.204.128 -
PRODUCTION_USER
deploy -
PRODUCTION_PATH
/opt/odapi
Optional Secrets (for notifications):
- SLACK_WEBHOOK_URL (if using Slack notifications)
- DISCORD_WEBHOOK_URL (if using Discord notifications)
Step 4: Set Up GitHub Environment
Go to: Settings → Environments → New environment
Create environment: production
Protection rules (recommended):
- ✅ Required reviewers: Add yourself
- ✅ Wait timer: 0 minutes
- ✅ Deployment branches: Only
main
This adds an approval step before production deployments.
Step 5: Verify Deployment Scripts on Server
Scripts should already be in place at:
/opt/odapi/scripts/deploy-production.sh/opt/odapi/scripts/rollback-production.sh
Verify they're executable:
ssh deploy@91.98.204.128 "ls -la /opt/odapi/scripts/*.sh"
Should show -rwxr-xr-x (executable).
How It Works
Automatic Deployment (Push to main)
# Local machine
git add .
git commit -m "feat: add new feature"
git push origin main
What happens:
- GitHub Actions detects push to
main - Runs deployment workflow
- SSHs to production server
- Executes deployment script:
- Pulls latest code
- Builds Docker image with commit hash
- Runs database migrations
- Restarts services
- Health check (30 retries)
- Auto-rollback if health check fails
Timeline: ~3-5 minutes
Manual Deployment (From Local Machine)
# Using rake task
bundle exec rake remote:deploy
# Prompts for confirmation, then deploys
Manual Deployment (GitHub UI)
- Go to Actions tab in GitHub
- Select Deploy to Production workflow
- Click Run workflow
- Select branch:
main - Click Run workflow
CI Pipeline (Pull Requests)
When you create a PR:
git checkout -b feature/new-feature
git push origin feature/new-feature
# Create PR on GitHub
Runs automatically:
- RSpec Tests - Full test suite with PostgreSQL & Redis
- RuboCop - Code style checks
- Brakeman - Security vulnerability scan
PR must pass all checks before merging to main.
Rollback Process
If Deployment Fails
Automatic rollback happens if:
- Health check fails after 30 attempts (60 seconds)
- Migration fails
- Docker build fails
Manual Rollback
View recent commits:
bundle exec rake remote:commits
Rollback to specific commit:
bundle exec rake remote:rollback[abc123f]
# Or SSH to server
ssh deploy@91.98.204.128
cd /opt/odapi
./scripts/rollback-production.sh abc123f
Monitoring Deployments
View GitHub Actions Logs
- Go to Actions tab in GitHub
- Click on latest workflow run
- Expand deployment steps to see logs
Monitor from Local Machine
# Watch deployment logs
bundle exec rake remote:logs
# Check service status
bundle exec rake remote:status
# Health check
bundle exec rake remote:health
Deployment Workflow Details
What Gets Deployed
- All code from
mainbranch - Environment variables from
.env(already on server) - Docker image rebuild with latest code
- Database migrations (if any)
What Doesn't Change
.envfile (persists between deployments)- Database data (migrations only, no data loss)
- Redis data (persists)
- Elasticsearch indices (persist)
- SSL certificates (managed by Traefik)
Zero-Downtime Strategy
- New image built while old container runs
- Migrations run before restart
- Services restart with new image
- Health check verifies application
- Old images cleaned up after success
Troubleshooting
Deployment Fails at Health Check
# Check logs
bundle exec rake remote:logs
# SSH to server and investigate
ssh deploy@91.98.204.128
cd /opt/odapi
docker compose -f docker-compose.production.yml ps
docker compose -f docker-compose.production.yml logs rails
SSH Connection Issues
# Test GitHub Actions SSH key
ssh -i ~/.ssh/github_actions_deploy deploy@91.98.204.128
# Verify key is in authorized_keys
ssh deploy@91.98.204.128 "cat ~/.ssh/authorized_keys | grep github-actions"
Migration Fails
Migrations run automatically during deployment. If they fail:
# SSH to server and check migration status
ssh deploy@91.98.204.128
cd /opt/odapi
docker compose -f docker-compose.production.yml run --rm rails bundle exec rails db:migrate:status
# Fix and re-run manually
docker compose -f docker-compose.production.yml run --rm rails bundle exec rails db:migrate
GitHub Actions Workflow Not Triggering
Check:
- Workflow file in
.github/workflows/ - Branch name matches trigger (
main) - GitHub Actions enabled in repo settings
Best Practices
Before Pushing to Main
-
Test locally
bundle exec rspec
bundle exec rubocop
brakeman -
Create PR first
- Push to feature branch
- Create pull request
- Wait for CI checks
- Get code review
- Merge to main
-
Small, incremental changes
- Easier to rollback if needed
- Faster deployment
- Lower risk
Deployment Checklist
- All tests passing locally
- RuboCop clean
- Brakeman clean
- Database migrations tested
- Feature tested in development
- Breaking changes documented
- Dependent services updated (if needed)
Post-Deployment
# Verify deployment
bundle exec rake remote:health
bundle exec rake remote:status
# Monitor logs for 5 minutes
bundle exec rake remote:logs
# Smoke test critical features
curl https://api.bizradar.fr/up
# Test other critical endpoints
Emergency Procedures
Critical Bug in Production
-
Immediate rollback
bundle exec rake remote:rollback[previous-commit-hash] -
Fix on hotfix branch
git checkout main
git pull
git checkout -b hotfix/critical-bug
# Fix the bug
git commit -m "hotfix: fix critical bug"
git push origin hotfix/critical-bug -
Emergency deploy (skip PR if critical)
git checkout main
git merge hotfix/critical-bug
git push origin main
# Auto-deploys via GitHub Actions
Server Issues
If server is unresponsive:
# Check server status
ssh deploy@91.98.204.128 "uptime && df -h && free -h"
# Restart all services
bundle exec rake remote:restart_all
# Last resort: restart server (via Hetzner console)
Customization
Add Slack Notifications
Edit .github/workflows/deploy-production.yml:
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
Change Deployment Branch
Edit workflow trigger:
on:
push:
branches: [ production ] # Change from 'main'
Skip CI on Specific Commits
git commit -m "docs: update README [skip ci]"
Useful Commands Reference
# Deployment
bundle exec rake remote:deploy # Manual deploy
bundle exec rake remote:rollback[hash] # Rollback
bundle exec rake remote:commits # View commits
# Monitoring
bundle exec rake remote:status # Service status
bundle exec rake remote:health # Health check
bundle exec rake remote:logs # Tail logs
bundle exec rake remote:stats # Resource usage
# Management
bundle exec rake remote:console # Rails console
bundle exec rake remote:run[task,args] # Run rake task
bundle exec rake remote:migrate # Run migrations
Architecture Diagram
┌─────────────────┐
│ Developer │
│ (Local Machine)│
└────────┬────────┘
│ git push origin main
│
┌────────▼────────┐
│ GitHub │
│ (Repository) │
└────────┬────────┘
│ Webhook trigger
│
┌────────▼────────┐
│ GitHub Actions │
│ (CI/CD Runner) │
└────────┬────────┘
│ SSH connection
│
┌────────▼────────────────────┐
│ Production Server │
│ (91.98.204.128) │
│ │
│ 1. Pull latest code │
│ 2. Build Docker image │
│ 3. Run migrations │
│ 4. Restart services │
│ 5. Health check │
│ 6. Success/Rollback │
└─────────────────────────────┘
Next Steps
- ✅ Complete GitHub secrets setup
- ✅ Test deployment with small change
- ✅ Set up monitoring (optional)
- ✅ Configure notifications (optional)
- ✅ Document team deployment process
Support
For issues:
- Check GitHub Actions logs
- Check server logs:
bundle exec rake remote:logs - Review this documentation
- Check deployment script:
/opt/odapi/scripts/deploy-production.sh