GitHub Actions — CI/CD Complete Guide

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

GitHub Actions — CI/CD Complete Guide

GitHub Actions automates your development workflow with CI/CD pipelines, testing, and deployment directly in GitHub.

Introduction

GitHub Actions enables automation triggered by repository events. Define workflows in YAML to test, build, and deploy code.

Workflow Basics

Simple Workflow

Create .github/workflows/test.yml:

name: Run Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
    - run: npm install
    - run: npm test

Workflow Events

on:
  push:
    branches:
      - main
      - develop
    paths:
      - 'src/**'
      - 'package.json'

  pull_request:
    branches:
      - main

  schedule:
    - cron: '0 0 * * *'  # Daily at midnight

  workflow_dispatch:  # Manual trigger

  release:
    types: [published]

Jobs and Steps

Multiple Jobs

name: Build and Deploy

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.tags }}
    steps:
    - uses: actions/checkout@v3
    - name: Build Docker image
      id: meta
      run: echo "tags=myapp:${{ github.sha }}" >> $GITHUB_OUTPUT

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
    - name: Deploy
      run: echo "Deploying ${{ needs.build.outputs.image_tag }}"

Matrix Builds

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm test

Environment and Secrets

Using Secrets

steps:
- name: Deploy to server
  env:
    SSH_KEY: ${{ secrets.DEPLOY_KEY }}
    API_KEY: ${{ secrets.API_KEY }}
  run: |
    mkdir -p ~/.ssh
    echo "$SSH_KEY" > ~/.ssh/id_rsa
    chmod 600 ~/.ssh/id_rsa
    ssh user@server './deploy.sh'

Environment Variables

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Print environment
      run: echo "Building $IMAGE_NAME:latest"

Docker Integration

Build and Push

name: Build and Push Docker Image

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Login to Docker Hub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: |
          ${{ secrets.DOCKER_USERNAME }}/myapp:latest
          ${{ secrets.DOCKER_USERNAME }}/myapp:${{ github.ref_name }}

Deployment

Deploy to AWS

name: Deploy to AWS

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      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 ECS
      run: |
        aws ecs update-service \
          --cluster my-cluster \
          --service my-service \
          --force-new-deployment

Deploy to Kubernetes

name: Deploy to Kubernetes

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up kubectl
      uses: azure/setup-kubectl@v3
      with:
        version: 'v1.26.0'

    - name: Configure kubectl
      run: |
        mkdir -p $HOME/.kube
        echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
        chmod 600 $HOME/.kube/config

    - name: Deploy
      run: kubectl apply -f k8s/

Testing

Node.js Testing

name: Test Node.js App

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
    - run: npm install
    - run: npm test
    - run: npm run lint

Artifacts and Caching

steps:
- uses: actions/checkout@v3

- name: Cache dependencies
  uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}

- name: Build
  run: npm run build

- name: Upload artifacts
  uses: actions/upload-artifact@v3
  with:
    name: dist
    path: dist/

Best Practices

name: Production Workflow

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
    - run: npm ci
    - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
    - run: npm ci
    - run: npm test
    - uses: codecov/codecov-action@v3

  build:
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Build
      run: npm run build

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    steps:
    - uses: actions/checkout@v3
    - name: Deploy to production
      run: echo "Deploying to production"

FAQ

Q: How do I keep secrets safe in GitHub Actions? A: Use GitHub Secrets. Never log secrets or commit them to repository. Use masked values in logs.

Q: Can I reuse workflows across repositories? A: Yes, use uses: with owner/repo/.github/workflows/workflow.yml@ref to call reusable workflows.

Q: How do I debug a failing workflow? A: Enable debug logging: secrets.ACTIONS_STEP_DEBUG set to true, then check logs for detailed output.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro