← กลับ
GitHub ActionsCI/CDAdvanced

GitHub Actions ขั้น Advanced — matrix, cache, reusable workflows

ตั้ง CI ตัวแรกได้แล้ว step ต่อไปคือทำให้เร็วขึ้น แชร์ workflow ระหว่างหลาย repo รัน test หลาย version พร้อมกัน

2026-01-05อ่าน 6 นาทีใหม่

ทำไมต้องไปต่อจากพื้นฐาน

ถ้าตั้ง GitHub Actions ครั้งแรก แล้วใช้สักพัก คุณจะเริ่มเจอปัญหา:

  • CI ช้าnpm install ใช้ 2 นาทีทุกครั้ง
  • อยาก test หลาย Node version — ต้อง copy job ซ้ำ
  • มี repo หลายตัวที่ workflow คล้ายกัน — copy ไปทุก repo เปลี่ยนทีต้องแก้ทุกที่
  • อยากให้ deploy ต้อง approve ก่อน

บทความนี้แก้ปัญหาพวกนี้ทีละข้อ

1. Cache dependencies — ทำให้ CI เร็วขึ้น 5-10 เท่า

actions/setup-node มี cache built-in — เปิดด้วย option เดียว:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'   # หรือ 'yarn', 'pnpm'

cache จะเก็บ ~/.npm ไว้ใน GitHub workflow ถัดไป hit cache ใช้เวลา 5-10 วินาทีแทน 60-120

ถ้าต้องการ cache อย่างอื่น (เช่น Next.js build cache):

- uses: actions/cache@v4
  with:
    path: |
      .next/cache
      ~/.cache/Cypress
    key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
    restore-keys: |
      ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-

key ตรง = hit cache เต็ม ๆ restore-keys ใช้เมื่อ key ไม่ตรงเป๊ะ = partial cache ดีกว่าไม่มี

2. Matrix — รัน test หลาย version พร้อมกัน

ทดสอบกับ Node 18, 20, 22 พร้อมกัน + แต่ละ OS:

jobs:
  test:
    strategy:
      fail-fast: false   # ตัวอื่นรันต่อได้ถ้ามีตัวพัง
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node: [18, 20, 22]
        exclude:
          - os: windows-latest
            node: 18

    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

GitHub จะรันทุก combo (3 OS × 3 Node = 9 job) พร้อมกัน บน runner คนละตัว

fail-fast: false สำคัญ — default ถ้า job ใดพัง job อื่นจะถูก cancel

3. Conditional steps

steps:
  - run: npm test

  - name: Upload coverage
    if: success() && matrix.node == '20' && matrix.os == 'ubuntu-latest'
    run: npm run coverage:upload

  - name: Notify on failure
    if: failure()
    run: |
      curl -X POST $SLACK_WEBHOOK -d '{"text":"Build failed"}'

useful function:

  • success() — งานทั้งหมดก่อนหน้าผ่าน (default)
  • failure() — มีบางอันล้ม
  • always() — รันเสมอ
  • cancelled() — ถ้างานถูก cancel

4. Job dependencies

jobs:
  lint:
    runs-on: ubuntu-latest
    steps: ...

  test:
    runs-on: ubuntu-latest
    steps: ...

  build:
    needs: [lint, test]   # รันต่อเมื่อทั้งสองผ่าน
    runs-on: ubuntu-latest
    steps: ...

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps: ...

graph แบบนี้:

lint ─┐
       ├─→ build → deploy
test ─┘

5. Artifacts — แชร์ไฟล์ระหว่าง job

build ใน job หนึ่ง ใช้ใน job อื่น:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - run: ./deploy.sh

6. Reusable workflow — แชร์ระหว่าง repo

สร้าง repo myorg/.github มี .github/workflows/node-ci.yml:

name: Reusable Node CI
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
    secrets:
      NPM_TOKEN:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

ใช้จาก repo อื่นใน org เดียวกัน:

# .github/workflows/ci.yml ใน repo อื่น
jobs:
  ci:
    uses: myorg/.github/.github/workflows/node-ci.yml@main
    with:
      node-version: '20'
    secrets:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

แก้ครั้งเดียวที่ repo .github ใช้ได้กับทุก repo

7. Composite actions — แชร์ steps

ถ้าแค่ 2-3 step ที่ใช้ซ้ำ ไม่ต้องสร้าง reusable workflow ใช้ composite action:

ที่ myorg/setup-app/action.yml:

name: Setup App
description: Checkout, install Node, install deps
inputs:
  node-version:
    default: '20'
runs:
  using: composite
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
    - run: npm ci
      shell: bash

ใช้:

- uses: myorg/setup-app@v1
  with:
    node-version: '20'

8. Environments — ต้อง approve ก่อน deploy

ตั้งใน Settings → Environments → New environment

  • ชื่อ "production"
  • เปิด Required reviewers เลือกคนที่อนุมัติได้
  • จำกัดเฉพาะ branch main

ใช้ใน workflow:

deploy-prod:
  runs-on: ubuntu-latest
  environment:
    name: production
    url: https://yourdomain.com
  steps:
    - run: ./deploy-prod.sh

push เข้า main → workflow รันถึง deploy-prod → รอคนกด approve → deploy

9. Manual trigger พร้อม input

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Where to deploy'
        type: choice
        options: [staging, production]
        default: staging
      version:
        description: 'Version to deploy'
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}"

ใน UI กด Run workflow เลือก option ได้

10. Concurrency — กัน run พร้อมกัน

ป้องกัน 2 deploy พร้อมกันที่ environment เดียวกัน:

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false   # รอตัวเก่าเสร็จก่อน

ถ้า cancel-in-progress: true ตัวเก่าจะถูก cancel — ใช้กับ CI ที่อยาก run แค่ commit ล่าสุด

11. ลด cost ด้วย self-hosted runner

GitHub-hosted runner ฟรีจำกัด (free tier ปกติ 2,000 min/เดือน) ถ้า private repo ใช้เยอะ — ตั้ง self-hosted runner บน VPS ของตัวเอง

runs-on: self-hosted

ตั้ง: Settings → Actions → Runners → New self-hosted runner ทำตามคำสั่งที่ให้

ดี: ฟรี, เร็วถ้ามี cache local; เสีย: ต้อง maintain เอง security ต้องดูแล

12. Security ของ workflow

Pin action เป็น SHA (ไม่ใช่ tag) สำหรับ third-party action:

# ❌ tag เปลี่ยนได้
- uses: some-author/action@v1

# ✅ SHA เปลี่ยนไม่ได้
- uses: some-author/action@a1b2c3d4e5f6...

action ของ GitHub เอง (actions/*) ใช้ tag ได้ — เขา audit แล้ว

ใช้ minimum permission:

permissions:
  contents: read       # อ่านอย่างเดียว
  pull-requests: write # comment PR ได้

ตัวอย่าง workflow ที่ครบครัน

name: CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  test:
    strategy:
      matrix:
        node: [20, 22]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with: { name: dist, path: dist/ }

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://yourdomain.com
    steps:
      - uses: actions/download-artifact@v4
        with: { name: dist, path: dist/ }
      - uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/myapp && git pull && pm2 reload ecosystem.config.js

สรุป

เริ่มจาก simple workflow แล้วค่อยใส่ feature ตามที่เจอปัญหาจริง — อย่า over-engineer ตั้งแต่แรก

ที่ส่งผลมากที่สุด: cache (เร็วขึ้นทันที) + environment (กัน deploy ผิด) + reusable workflow (เมื่อ scale ทีม)

← ดูบทความอื่น