Back to Blog
GitHub ActionsCI/CDDevOpsAutomation

GitHub Actions for CI/CD

Automate CI/CD with GitHub Actions. From basic workflows to testing to deployment patterns.

B
Bootspring Team
Engineering
August 20, 2021
5 min read

GitHub Actions automates testing and deployment. Here's how to set up effective workflows.

Basic Workflow#

1# .github/workflows/ci.yml 2name: CI 3 4on: 5 push: 6 branches: [main, develop] 7 pull_request: 8 branches: [main] 9 10jobs: 11 build: 12 runs-on: ubuntu-latest 13 14 steps: 15 - name: Checkout code 16 uses: actions/checkout@v4 17 18 - name: Setup Node.js 19 uses: actions/setup-node@v4 20 with: 21 node-version: '20' 22 cache: 'npm' 23 24 - name: Install dependencies 25 run: npm ci 26 27 - name: Run linter 28 run: npm run lint 29 30 - name: Run tests 31 run: npm test 32 33 - name: Build 34 run: npm run build

Matrix Testing#

1name: Test Matrix 2 3on: [push, pull_request] 4 5jobs: 6 test: 7 runs-on: ${{ matrix.os }} 8 9 strategy: 10 matrix: 11 os: [ubuntu-latest, windows-latest, macos-latest] 12 node-version: [18, 20, 22] 13 exclude: 14 - os: windows-latest 15 node-version: 18 16 17 steps: 18 - uses: actions/checkout@v4 19 20 - name: Use Node.js ${{ matrix.node-version }} 21 uses: actions/setup-node@v4 22 with: 23 node-version: ${{ matrix.node-version }} 24 25 - run: npm ci 26 - run: npm test

Caching Dependencies#

1jobs: 2 build: 3 runs-on: ubuntu-latest 4 5 steps: 6 - uses: actions/checkout@v4 7 8 - name: Setup Node.js 9 uses: actions/setup-node@v4 10 with: 11 node-version: '20' 12 cache: 'npm' # Built-in npm caching 13 14 # Or manual caching 15 - name: Cache node modules 16 uses: actions/cache@v4 17 with: 18 path: ~/.npm 19 key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 20 restore-keys: | 21 ${{ runner.os }}-node- 22 23 - run: npm ci

Environment Variables and Secrets#

1jobs: 2 deploy: 3 runs-on: ubuntu-latest 4 5 env: 6 NODE_ENV: production 7 8 steps: 9 - uses: actions/checkout@v4 10 11 - name: Deploy 12 env: 13 API_KEY: ${{ secrets.API_KEY }} 14 DATABASE_URL: ${{ secrets.DATABASE_URL }} 15 run: | 16 echo "Deploying with API_KEY" 17 npm run deploy 18 19 # Using GitHub environment 20 - name: Deploy to production 21 environment: 22 name: production 23 url: https://example.com 24 run: npm run deploy:prod

Parallel and Sequential Jobs#

1jobs: 2 lint: 3 runs-on: ubuntu-latest 4 steps: 5 - uses: actions/checkout@v4 6 - run: npm ci 7 - run: npm run lint 8 9 test: 10 runs-on: ubuntu-latest 11 steps: 12 - uses: actions/checkout@v4 13 - run: npm ci 14 - run: npm test 15 16 # Runs after lint and test succeed 17 build: 18 needs: [lint, test] 19 runs-on: ubuntu-latest 20 steps: 21 - uses: actions/checkout@v4 22 - run: npm ci 23 - run: npm run build 24 25 # Runs after build 26 deploy: 27 needs: build 28 runs-on: ubuntu-latest 29 if: github.ref == 'refs/heads/main' 30 steps: 31 - uses: actions/checkout@v4 32 - run: npm run deploy

Artifacts#

1jobs: 2 build: 3 runs-on: ubuntu-latest 4 steps: 5 - uses: actions/checkout@v4 6 - run: npm ci 7 - run: npm run build 8 9 - name: Upload build artifacts 10 uses: actions/upload-artifact@v4 11 with: 12 name: build 13 path: dist/ 14 retention-days: 5 15 16 deploy: 17 needs: build 18 runs-on: ubuntu-latest 19 steps: 20 - name: Download build artifacts 21 uses: actions/download-artifact@v4 22 with: 23 name: build 24 path: dist/ 25 26 - name: Deploy 27 run: | 28 ls -la dist/ 29 # Deploy dist folder

Docker Build and Push#

1name: Docker 2 3on: 4 push: 5 branches: [main] 6 tags: ['v*'] 7 8jobs: 9 build: 10 runs-on: ubuntu-latest 11 12 steps: 13 - uses: actions/checkout@v4 14 15 - name: Set up Docker Buildx 16 uses: docker/setup-buildx-action@v3 17 18 - name: Login to Docker Hub 19 uses: docker/login-action@v3 20 with: 21 username: ${{ secrets.DOCKER_USERNAME }} 22 password: ${{ secrets.DOCKER_TOKEN }} 23 24 - name: Extract metadata 25 id: meta 26 uses: docker/metadata-action@v5 27 with: 28 images: myuser/myapp 29 tags: | 30 type=ref,event=branch 31 type=semver,pattern={{version}} 32 type=sha 33 34 - name: Build and push 35 uses: docker/build-push-action@v5 36 with: 37 context: . 38 push: true 39 tags: ${{ steps.meta.outputs.tags }} 40 labels: ${{ steps.meta.outputs.labels }} 41 cache-from: type=gha 42 cache-to: type=gha,mode=max

Deploy to Cloud Platforms#

1# Vercel 2name: Deploy to Vercel 3 4on: 5 push: 6 branches: [main] 7 8jobs: 9 deploy: 10 runs-on: ubuntu-latest 11 steps: 12 - uses: actions/checkout@v4 13 14 - name: Deploy to Vercel 15 uses: amondnet/vercel-action@v25 16 with: 17 vercel-token: ${{ secrets.VERCEL_TOKEN }} 18 vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} 19 vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} 20 vercel-args: '--prod' 21 22# AWS S3 23name: Deploy to S3 24 25on: 26 push: 27 branches: [main] 28 29jobs: 30 deploy: 31 runs-on: ubuntu-latest 32 steps: 33 - uses: actions/checkout@v4 34 35 - name: Configure AWS credentials 36 uses: aws-actions/configure-aws-credentials@v4 37 with: 38 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 39 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 40 aws-region: us-east-1 41 42 - run: npm ci && npm run build 43 44 - name: Deploy to S3 45 run: aws s3 sync dist/ s3://my-bucket --delete 46 47 - name: Invalidate CloudFront 48 run: | 49 aws cloudfront create-invalidation \ 50 --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \ 51 --paths "/*"

Pull Request Workflows#

1name: PR Check 2 3on: 4 pull_request: 5 types: [opened, synchronize, reopened] 6 7jobs: 8 check: 9 runs-on: ubuntu-latest 10 11 steps: 12 - uses: actions/checkout@v4 13 14 - uses: actions/setup-node@v4 15 with: 16 node-version: '20' 17 cache: 'npm' 18 19 - run: npm ci 20 - run: npm run lint 21 - run: npm test 22 23 # Comment test coverage 24 - name: Coverage Report 25 uses: codecov/codecov-action@v4 26 with: 27 token: ${{ secrets.CODECOV_TOKEN }} 28 29 # Add label based on files changed 30 - uses: actions/labeler@v5 31 with: 32 repo-token: ${{ secrets.GITHUB_TOKEN }}

Scheduled Workflows#

1name: Scheduled Tasks 2 3on: 4 schedule: 5 # Every day at midnight UTC 6 - cron: '0 0 * * *' 7 workflow_dispatch: # Allow manual trigger 8 9jobs: 10 cleanup: 11 runs-on: ubuntu-latest 12 steps: 13 - uses: actions/checkout@v4 14 15 - name: Cleanup old artifacts 16 run: | 17 # Cleanup script 18 19 security-scan: 20 runs-on: ubuntu-latest 21 steps: 22 - uses: actions/checkout@v4 23 24 - name: Run security audit 25 run: npm audit 26 27 - name: Dependency check 28 uses: dependency-check/Dependency-Check_Action@main

Reusable Workflows#

1# .github/workflows/reusable-build.yml 2name: Reusable Build 3 4on: 5 workflow_call: 6 inputs: 7 node-version: 8 required: false 9 type: string 10 default: '20' 11 secrets: 12 npm-token: 13 required: false 14 15jobs: 16 build: 17 runs-on: ubuntu-latest 18 steps: 19 - uses: actions/checkout@v4 20 21 - uses: actions/setup-node@v4 22 with: 23 node-version: ${{ inputs.node-version }} 24 registry-url: 'https://registry.npmjs.org' 25 26 - run: npm ci 27 env: 28 NODE_AUTH_TOKEN: ${{ secrets.npm-token }} 29 30 - run: npm run build 31 32# Using the reusable workflow 33# .github/workflows/main.yml 34name: Main 35 36on: [push] 37 38jobs: 39 call-build: 40 uses: ./.github/workflows/reusable-build.yml 41 with: 42 node-version: '20' 43 secrets: 44 npm-token: ${{ secrets.NPM_TOKEN }}

Best Practices#

1# Use specific action versions 2- uses: actions/checkout@v4.1.1 # Not @v4 or @main 3 4# Fail fast 5strategy: 6 fail-fast: true 7 8# Set timeouts 9jobs: 10 build: 11 timeout-minutes: 15 12 13# Concurrency control 14concurrency: 15 group: ${{ github.workflow }}-${{ github.ref }} 16 cancel-in-progress: true 17 18# Conditional execution 19- name: Deploy 20 if: github.event_name == 'push' && github.ref == 'refs/heads/main'

Conclusion#

GitHub Actions provides powerful CI/CD automation. Use caching to speed up builds, parallelize independent jobs, and leverage environments for deployment approvals. Keep workflows modular with reusable workflows and always pin action versions for reliability.

Share this article

Help spread the word about Bootspring