Back to Blog
GitHub ActionsCI/CDDevOpsAutomation

GitHub Actions: Complete Workflow Guide

Automate CI/CD with GitHub Actions. From basic workflows to matrix builds to deployment strategies.

B
Bootspring Team
Engineering
July 28, 2022
5 min read

GitHub Actions automates your development workflows. Here's how to build effective CI/CD pipelines.

Basic Workflow Structure#

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 tests 28 run: npm test 29 30 - name: Build 31 run: npm run build

Caching Dependencies#

1jobs: 2 build: 3 runs-on: ubuntu-latest 4 5 steps: 6 - uses: actions/checkout@v4 7 8 # Node.js with built-in caching 9 - uses: actions/setup-node@v4 10 with: 11 node-version: '20' 12 cache: 'npm' 13 14 # Or manual cache control 15 - name: Cache node modules 16 uses: actions/cache@v4 17 id: cache-npm 18 with: 19 path: ~/.npm 20 key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} 21 restore-keys: | 22 ${{ runner.os }}-npm- 23 24 - name: Install dependencies 25 if: steps.cache-npm.outputs.cache-hit != 'true' 26 run: npm ci 27 28 # Cache build outputs 29 - name: Cache build 30 uses: actions/cache@v4 31 with: 32 path: .next/cache 33 key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }} 34 restore-keys: | 35 ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-

Matrix Builds#

1jobs: 2 test: 3 runs-on: ${{ matrix.os }} 4 5 strategy: 6 fail-fast: false 7 matrix: 8 os: [ubuntu-latest, windows-latest, macos-latest] 9 node: [18, 20, 22] 10 exclude: 11 - os: windows-latest 12 node: 18 13 14 steps: 15 - uses: actions/checkout@v4 16 17 - name: Setup Node.js ${{ matrix.node }} 18 uses: actions/setup-node@v4 19 with: 20 node-version: ${{ matrix.node }} 21 22 - run: npm ci 23 - run: npm test 24 25 # Require all matrix jobs to pass 26 test-complete: 27 needs: test 28 runs-on: ubuntu-latest 29 steps: 30 - run: echo "All tests passed"

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 # Using secrets 12 - name: Deploy 13 env: 14 DATABASE_URL: ${{ secrets.DATABASE_URL }} 15 API_KEY: ${{ secrets.API_KEY }} 16 run: npm run deploy 17 18 # Using environments 19 - name: Deploy to Production 20 uses: some-action@v1 21 with: 22 environment: production 23 env: 24 API_URL: ${{ vars.API_URL }} # Environment variable 25 SECRET_KEY: ${{ secrets.SECRET_KEY }} # Secret

Conditional Jobs#

1jobs: 2 test: 3 runs-on: ubuntu-latest 4 steps: 5 - uses: actions/checkout@v4 6 - run: npm test 7 8 deploy-staging: 9 needs: test 10 if: github.ref == 'refs/heads/develop' 11 runs-on: ubuntu-latest 12 steps: 13 - run: echo "Deploy to staging" 14 15 deploy-production: 16 needs: test 17 if: github.ref == 'refs/heads/main' 18 runs-on: ubuntu-latest 19 environment: production 20 steps: 21 - run: echo "Deploy to production" 22 23 # Conditional steps 24 build: 25 runs-on: ubuntu-latest 26 steps: 27 - uses: actions/checkout@v4 28 29 - name: Build for PR 30 if: github.event_name == 'pull_request' 31 run: npm run build:preview 32 33 - name: Build for production 34 if: github.ref == 'refs/heads/main' 35 run: npm run build:production

Reusable Workflows#

1# .github/workflows/deploy-template.yml 2name: Deploy Template 3 4on: 5 workflow_call: 6 inputs: 7 environment: 8 required: true 9 type: string 10 url: 11 required: true 12 type: string 13 secrets: 14 deploy_key: 15 required: true 16 17jobs: 18 deploy: 19 runs-on: ubuntu-latest 20 environment: 21 name: ${{ inputs.environment }} 22 url: ${{ inputs.url }} 23 24 steps: 25 - uses: actions/checkout@v4 26 - run: npm ci 27 - run: npm run build 28 29 - name: Deploy 30 env: 31 DEPLOY_KEY: ${{ secrets.deploy_key }} 32 run: ./deploy.sh ${{ inputs.environment }}
1# .github/workflows/deploy-staging.yml 2name: Deploy Staging 3 4on: 5 push: 6 branches: [develop] 7 8jobs: 9 deploy: 10 uses: ./.github/workflows/deploy-template.yml 11 with: 12 environment: staging 13 url: https://staging.example.com 14 secrets: 15 deploy_key: ${{ secrets.STAGING_DEPLOY_KEY }}

Docker Build and Push#

1jobs: 2 docker: 3 runs-on: ubuntu-latest 4 5 steps: 6 - uses: actions/checkout@v4 7 8 - name: Set up Docker Buildx 9 uses: docker/setup-buildx-action@v3 10 11 - name: Login to Container Registry 12 uses: docker/login-action@v3 13 with: 14 registry: ghcr.io 15 username: ${{ github.actor }} 16 password: ${{ secrets.GITHUB_TOKEN }} 17 18 - name: Extract metadata 19 id: meta 20 uses: docker/metadata-action@v5 21 with: 22 images: ghcr.io/${{ github.repository }} 23 tags: | 24 type=ref,event=branch 25 type=ref,event=pr 26 type=sha,prefix= 27 type=semver,pattern={{version}} 28 29 - name: Build and push 30 uses: docker/build-push-action@v5 31 with: 32 context: . 33 push: ${{ github.event_name != 'pull_request' }} 34 tags: ${{ steps.meta.outputs.tags }} 35 labels: ${{ steps.meta.outputs.labels }} 36 cache-from: type=gha 37 cache-to: type=gha,mode=max

Release Workflow#

1name: Release 2 3on: 4 push: 5 tags: 6 - 'v*' 7 8jobs: 9 release: 10 runs-on: ubuntu-latest 11 12 steps: 13 - uses: actions/checkout@v4 14 with: 15 fetch-depth: 0 16 17 - name: Setup Node.js 18 uses: actions/setup-node@v4 19 with: 20 node-version: '20' 21 registry-url: 'https://registry.npmjs.org' 22 23 - run: npm ci 24 - run: npm run build 25 - run: npm test 26 27 - name: Publish to npm 28 run: npm publish 29 env: 30 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 32 - name: Create GitHub Release 33 uses: softprops/action-gh-release@v1 34 with: 35 generate_release_notes: true 36 files: | 37 dist/*.js 38 dist/*.d.ts

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: Clean old artifacts 16 run: ./scripts/cleanup.sh 17 18 - name: Update dependencies 19 run: npm update 20 21 - name: Create PR if changes 22 uses: peter-evans/create-pull-request@v5 23 with: 24 title: 'chore: update dependencies' 25 branch: auto-update-deps

Composite Actions#

1# .github/actions/setup-project/action.yml 2name: Setup Project 3description: Setup Node.js and install dependencies 4 5inputs: 6 node-version: 7 description: Node.js version 8 default: '20' 9 10runs: 11 using: composite 12 steps: 13 - name: Setup Node.js 14 uses: actions/setup-node@v4 15 with: 16 node-version: ${{ inputs.node-version }} 17 cache: 'npm' 18 19 - name: Install dependencies 20 shell: bash 21 run: npm ci 22 23 - name: Build 24 shell: bash 25 run: npm run build
1# Usage 2jobs: 3 test: 4 runs-on: ubuntu-latest 5 steps: 6 - uses: actions/checkout@v4 7 - uses: ./.github/actions/setup-project 8 - run: npm test

Best Practices#

Performance: ✓ Cache dependencies ✓ Use matrix for parallel jobs ✓ Cancel redundant runs ✓ Use appropriate runners Security: ✓ Use secrets for credentials ✓ Pin action versions ✓ Limit token permissions ✓ Use environments for approvals Organization: ✓ Use reusable workflows ✓ Create composite actions ✓ Document workflows ✓ Keep workflows focused

Conclusion#

GitHub Actions enables powerful CI/CD automation. Start with basic workflows, add caching for performance, use matrix builds for comprehensive testing, and implement reusable workflows to reduce duplication. Secure your pipelines with proper secret management and environment protections.

Share this article

Help spread the word about Bootspring