Skip to main content

GitHub Actions

GitHub Actions is GitHub’s CI/CD automation tool. It can automatically run workflows for building, testing, deployment, and more. Public repositories are free to use, while private repositories come with a monthly free quota.

Basic Concepts

  • Workflow: an automated process made up of one or more jobs
  • Job(Task):a set of steps executed on the same runner
  • Step: a single task, which can be either an action or a shell command
  • Action(Action):reusable smallest unit
  • Runner: the server that executes the workflow

Basic Syntax

Workflow configuration files use YAML and live in the .github/workflows/ directory.

Simplest workflow

name: Hello World

on: [push]

jobs:
  built:
    runs-on: ubuntu-latest
    steps:
      - name: Say hello
        run: echo "Hello, World!"

Complete example

name: CI

# 触发条件
on:
  push:
    branches: [ main, dev ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '0 0 * * 0'  # 每周日午夜
  workflow_dispatch:  # 手动触发

# Environment Variables
env:
  NODE_VERSION: 18

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
      
      - name: Install dependencies
        run: npm install
      
      - name: Run tests
        run: npm test

Trigger events

Push Event

on:
  push:
    branches:
      - main
      - 'releases/**'
    paths:
      - '**.js'
      - '!docs/**'
    tags:
      - v1.*

Pull Request Event

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [ main ]

Scheduled tasks

on:
  schedule:
    - cron: '30 5 * * 1-5'  # 工作日早上5:30

Manual trigger

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

Common actions

Checkout code

- uses: actions/checkout@v4
  with:
    fetch-depth: 0  # 获取完整History

Set up Node.js

- uses: actions/setup-node@v4
  with:
    node-version: '18'
    cache: 'npm'

Set up Python

- uses: actions/setup-python@v4
  with:
    python-version: '3.11'
    cache: 'pip'

CachingDependency

- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

Upload build artifacts

- uses: actions/upload-artifact@v3
  with:
    name: built-files
    path: dist/

Download build artifacts

- uses: actions/download-artifact@v3
  with:
    name: built-files
    path: dist/

Automatically publish a release

Create a release

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build
        run: |
          npm install
          npm run built
      
      - name: Create Release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/*
          body: |
            ## Changes
            - Feature 1
            - Feature 2
          draft: false
          prerelease: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

automatically generates Release Notes

- name: Create Release
  uses: actions/create-release@v1
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    tag_name: ${{ github.ref }}
    release_name: Release ${{ github.ref }}
    generate_release_notes: true

Self-hosted Runner

Self-hosted runners let you run workflows on your own servers.

Add a runner

  1. Go to Repository Settings → Actions → Runners → New self-hosted runner.
  2. Follow the prompts to install and configure it on your server.
  3. Start the runner.

Installation(Linux)

# 创建目录
mkdir actions-runner && cd actions-runner

# Download
curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz

# 解压
tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz

# Configuration
./config.sh --url https://github.com/user/repo --token YOUR_TOKEN

# Run
./run.sh

# 作为服务Run
sudo ./svc.sh install
sudo ./svc.sh start

Use a self-hosted runner

jobs:
  built:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - run: ./build.sh

Label selection

jobs:
  built:
    runs-on: [self-hosted, linux, x64]

Secrets management

Add a secret

Settings → Secrets and variables → Actions → New repository secret

Use a secret

steps:
  - name: Deploy
    env:
      API_KEY: ${{ secrets.API_KEY }}
    run: ./deploy.sh

Matrix builds

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

Conditional execution

steps:
  - name: Only on main
    if: github.ref == 'refs/heads/main'
    run: echo "Main branch"
  
  - name: Only on success
    if: success()
    run: echo "Previous steps succeeded"
  
  - name: Always run
    if: always()
    run: echo "Cleanup"

Practical examples

Automatically deploy to a server

name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/myapp
            git pull
            npm install
            pm2 restart myapp

Build and push Docker images

name: Docker Build

on:
  push:
    branches: [ main ]

jobs:
  built:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: user/app:latest

Code checks

name: Lint

on: [push, pull_request]

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

Best Practices

  1. Use caching to speed up dependency installation.
  2. Use matrix builds for multi-environment testing.
  3. Run jobs in parallel when they are independent.
  4. Use if wisely to save resources with conditional execution.
  5. Protect secrets and never print them in logs.
  6. Prefer official actions because they are more reliable and better maintained.
  7. Limit permissions and grant only what each workflow needs.

References

Last modified on April 17, 2026