GitHub Actions CI/CD: From Jenkins to Cloud-Native Pipelines
Our Jenkins server was a pain. Maintenance overhead, slow builds, plugin hell.
Migrated to GitHub Actions. Build time 15min → 5min, zero maintenance. Here’s how.
Table of Contents
The Problem
Jenkins Issues:
- Build time: 15min
- Server maintenance: 10h/month
- Plugin conflicts
- No auto-scaling
- Infrastructure cost: $500/month
Basic Workflow
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest --cov=. --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
Multi-Environment Deployment
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
default: 'staging'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push myapp:${{ github.sha }}
docker push myapp:latest
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
kubectl set image deployment/myapp myapp=myapp:${{ github.sha }} -n staging
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
kubectl set image deployment/myapp myapp=myapp:${{ github.sha }} -n production
Matrix Builds
# .github/workflows/matrix.yml
name: Matrix Build
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: pytest
Caching Dependencies
# .github/workflows/cache.yml
name: Build with Cache
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: pip install -r requirements.txt
Results:
- First build: 5min
- Cached build: 1min (-80%)
Secrets Management
# .github/workflows/secrets.yml
name: Deploy with Secrets
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 sync ./dist s3://my-bucket
Reusable Workflows
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy-key:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to ${{ inputs.environment }}
run: |
echo "Deploying to ${{ inputs.environment }}"
# Deploy logic here
Usage:
# .github/workflows/main.yml
name: Main
on: [push]
jobs:
deploy-staging:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: staging
secrets:
deploy-key: ${{ secrets.STAGING_DEPLOY_KEY }}
deploy-production:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
secrets:
deploy-key: ${{ secrets.PROD_DEPLOY_KEY }}
Custom Actions
# .github/actions/slack-notify/action.yml
name: 'Slack Notify'
description: 'Send notification to Slack'
inputs:
webhook-url:
description: 'Slack webhook URL'
required: true
message:
description: 'Message to send'
required: true
runs:
using: 'composite'
steps:
- run: |
curl -X POST ${{ inputs.webhook-url }} \
-H 'Content-Type: application/json' \
-d '{"text":"${{ inputs.message }}"}'
shell: bash
Usage:
- name: Notify Slack
uses: ./.github/actions/slack-notify
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
message: 'Deployment successful!'
Results
Build Performance:
| Metric | Jenkins | GitHub Actions | Improvement |
|---|---|---|---|
| Build time | 15min | 5min | 67% |
| Queue time | 5min | 0min | 100% |
| Total time | 20min | 5min | 75% |
Cost:
- Infrastructure: $500/month → $0
- Maintenance: 10h/month → 0h
- Total savings: $2000/month
Developer Experience:
- Setup time: 2 days → 1 hour
- Configuration: Complex → Simple
- Debugging: Hard → Easy
Lessons Learned
- Cloud-native better: Zero maintenance
- Matrix builds powerful: Test all combinations
- Caching essential: 80% faster builds
- Reusable workflows: DRY principle
- Free for public repos: Cost savings
Conclusion
GitHub Actions transformed our CI/CD. Build time 15min → 5min, zero maintenance, $2K/month savings.
Key takeaways:
- Build time: 15min → 5min (-67%)
- Infrastructure cost: $500 → $0/month
- Maintenance: 10h → 0h/month
- Setup time: 2 days → 1 hour
- Total savings: $2K/month
Migrate to GitHub Actions. It’s worth it.