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 buildMatrix 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 testCaching 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 ciEnvironment 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:prodParallel 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 deployArtifacts#
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 folderDocker 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=maxDeploy 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@mainReusable 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.