Skip to content
All essays
CraftMarch 21, 202412 min

Complete Guide to GitHub Actions for CI/CD

Master GitHub Actions to build powerful CI/CD pipelines that automate your development workflow

Ü
Ümit Uz
Mobile & Full Stack Developer

GitHub Actions has revolutionized how developers implement CI/CD pipelines. Integrated directly into GitHub, it provides a powerful platform for automating builds, tests, and deployments. Let's explore how to build robust CI/CD workflows with GitHub Actions.

Understanding GitHub Actions Fundamentals

GitHub Actions uses a YAML-based workflow definition that runs in response to events. Workflows contain jobs, which contain steps that execute commands or actions.

yaml
# .github/workflows/ci.yml name: CI Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Run tests run: npm test

Building Your First Workflow

Multi-Stage CI Pipeline

yaml
name: CI Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] env: NODE_VERSION: '20.x' CACHE_VERSION: 'v1' jobs: lint: name: Lint Code runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run ESLint run: npm run lint test: name: Run Tests runs-on: ubuntu-latest needs: lint strategy: matrix: node-version: [18.x, 20.x, 21.x] steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test -- --coverage - name: Upload coverage uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} build: name: Build Application runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build env: CI: true - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: build path: dist/ retention-days: 7

Advanced CI/CD Patterns

Caching Strategy

yaml
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Cache node modules uses: actions/cache@v3 id: npm-cache with: path: | ~/.npm node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies if: steps.npm-cache.outputs.cache-hit != 'true' run: npm ci

Matrix Builds

yaml
jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] node-version: [18.x, 20.x, 21.x] # Exclude specific combinations exclude: - os: windows-latest node-version: 21.x steps: - uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm test

Deployment Workflows

Deploy to Production

yaml
name: Deploy to Production on: push: branches: [ main ] jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://example.com steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Build application run: npm run build env: CI: false - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} vercel-args: '--prod' - name: Run smoke tests run: | curl -f https://example.com/health || exit 1

Blue-Green Deployment

yaml
jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 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 blue environment run: | # Deploy new version to blue environment aws elasticbeanstalk update-environment \ --environment-name myapp-blue \ --version-label $GITHUB_SHA - name: Run smoke tests on blue run: | # Test blue environment curl -f https://blue.example.com/health - name: Switch traffic to blue run: | # Swap DNS to point to blue aws route53 change-resource-record-sets \ --hosted-zone-id Z1234567890ABC \ --change-batch file://route53-switch.json

Docker and Kubernetes

Build and Push Docker Image

yaml
jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max

Deploy to Kubernetes

yaml
jobs: deploy-k8s: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Configure kubectl uses: azure/k8s-set-context@v4 with: method: kubeconfig kubeconfig: ${{ secrets.KUBE_CONFIG }} - name: Deploy to Kubernetes run: | kubectl set image deployment/myapp \ myapp=ghcr.io/username/repo:${{ github.sha }} \ --namespace=production - name: Verify deployment run: | kubectl rollout status deployment/myapp --namespace=production kubectl get pods -n production

Monitoring and Notifications

Slack Notifications

yaml
name: CI with Notifications on: push: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test notify: needs: test runs-on: ubuntu-latest if: always() steps: - name: Send Slack notification uses: slackapi/slack-github-action@v1.24.0 with: payload: | { "text": "CI Status: ${{ job.status }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "Build ${{ github.sha }} completed with status *${{ job.status }}*" } } ] } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Best Practices

Use Reusable Workflows

yaml
# .github/workflows/reusable-ci.yml name: Reusable CI Workflow on: workflow_call: inputs: node-version: required: true type: string outputs: test-result: value: ${{ jobs.test.outputs.result }} jobs: test: runs-on: ubuntu-latest outputs: result: ${{ steps.test.outputs.result }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - run: npm test - id: test run: echo "result=success" >> $GITHUB_OUTPUT

Security Scanning

yaml
jobs: security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy results to GitHub Security uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif'

Conclusion

GitHub Actions provides a powerful, flexible platform for CI/CD. Start with simple workflows, iterate based on your needs, and leverage the rich ecosystem of actions to build automation that saves time and improves quality.

Focus on making workflows fast, reliable, and secure. Use caching, parallel jobs, and smart deployment strategies to keep your development workflow smooth and efficient.

Next essay
Building Production-Grade Monitoring with Prometheus