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 buildCaching 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 }} # SecretConditional 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:productionReusable 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=maxRelease 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.tsScheduled 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-depsComposite 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 build1# 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 testBest 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.