---
id: cicd-github-actions
title: "GitHub Actions"
track: cicd-iac
trackTitle: "CI/CD và Infrastructure as Code"
order: 1
level: core
status: published
estimatedMinutes: 40
prerequisites:
  - linux-shell-scripting
  - docker-dockerfile
next: cicd-jenkins
reviewedAgainst: 2026-10-12
sources:
  - title: "Workflow syntax for GitHub Actions"
    url: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
  - title: "Secure use reference"
    url: https://docs.github.com/en/actions/reference/security/secure-use
  - title: "actions/checkout"
    url: https://github.com/actions/checkout
  - title: "actions/setup-node"
    url: https://github.com/actions/setup-node
---

# GitHub Actions

GitHub Actions chạy workflow trong repo mỗi lần có event. Mẫu dưới dùng `actions/checkout@v7` và `actions/setup-node@v7`. Node `24` là Active LTS theo lịch đã công bố lúc viết bài.

## Để làm gì

Mỗi lần push hoặc mở pull request, một runner sạch chạy `npm test`. Kết quả lấy từ lockfile và script trong repo. Máy dev còn sót package, hoặc ai đó quên chạy test, không đổi được kết quả này.

## Gồm những gì

- Workflow, job, step.
- Trigger `push` và `pull_request`.
- Checkout, cài Node, `npm ci`, `npm test`.
- `GITHUB_TOKEN` với permission tối thiểu.

Bài này chỉ là CI. Deploy image lên registry hoặc cluster là workflow khác. Thêm workflow đó sau khi test đã ổn, và cấp credential theo từng environment.

## Dùng khi nào

Repo đã có lockfile và một lệnh test chạy được thì mới gắn workflow này. Mẫu dưới cần `package-lock.json`. Thiếu file đó, hoặc lockfile lệch `package.json`, thì `npm ci` từ chối chạy.

Đừng cho workflow đầu tiên quyền write lên package, cloud, hay cluster. Job nào cần quyền đó thì khai báo trên đúng job đó.

## Kiến thức cốt lõi

Workflow là file YAML trong `.github/workflows/`. `on` chọn event. Không có `needs` thì các job chạy song song. Mỗi job nhận một runner mới. `ubuntu-latest` là nhãn, GitHub trỏ nhãn đó tới một image runner. Phần mềm cài sẵn trên image có trong tài liệu runner, và image đó có thể đổi.

Mỗi step là một lệnh `run`, hoặc một action qua `uses`. Action là một repo, và bạn gắn version trong `uses`. GitHub khuyên ghi version rõ. Tag major như `@v7` nhận bản vá trong major đó, nhưng tag có thể bị đẩy sang commit khác. Pin đủ commit SHA thì workflow đứng ở đúng bản đã review. Mẫu lab dùng tag major cho dễ đọc. Cách pin chặt hơn nằm ở mục lỗi thường gặp.

`permissions: contents: read` giữ `GITHUB_TOKEN` tự sinh của workflow này ở mức đọc source. Tài liệu secure use khuyên mặc định chỉ đọc contents, rồi nới riêng cho job nào cần ghi.

`npm ci` cài đúng lockfile trên thư mục sạch. `cache: npm` của `setup-node` lấy lockfile làm cache key. Cache giúp job nhanh hơn. Lockfile vẫn quyết version nào được cài.

## Lệnh và code mẫu

Lab để ở repo riêng, hoặc một thư mục mẫu. Repo quiz chưa có script `test` thì đừng gắn workflow này vào.

`package.json`:

```json
{
  "name": "devops-lab-ci",
  "private": true,
  "scripts": {
    "test": "node --test"
  }
}
```

`hello.test.js`:

```javascript
const test = require("node:test");
const assert = require("node:assert/strict");

test("lab assertion", () => {
  assert.equal("ok", "ok");
});
```

Tạo lockfile một lần trên máy dev, rồi commit cả `package.json` và `package-lock.json`:

```bash
npm install
npm test
```

`.github/workflows/ci.yml`:

```yaml
name: CI

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

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Test
        run: npm test
```

Workflow này không dùng secret. Step sau cần credential thì cất trong GitHub Secrets, đọc bằng `${{ secrets.TEN_SECRET }}`. Đừng ghi giá trị thẳng vào file YAML.

## Thực hành

1. Tạo repo lab với ba file ở trên, kèm lockfile.
2. Chạy `npm test` trên máy local trước khi push.
3. Push branch và mở pull request vào `main`.
4. Mở tab Actions, đọc log từng step.
5. Sửa assertion cho sai, push commit mới, xác nhận job fail. Rồi sửa lại.

## Kết quả mong đợi

- Job `test` xanh khi assertion đúng.
- Log có step Checkout, Setup Node.js, Install, Test.
- `npm ci` dùng `package-lock.json`.
- Assertion sai thì `npm test` trả exit code khác 0, cả job fail.
- Workflow không cần secret.

## Checklist

- [ ] Đặt file workflow trong `.github/workflows/`.
- [ ] Giải thích được `contents: read` giới hạn token của workflow.
- [ ] Commit lockfile cùng `package.json`.
- [ ] Đọc log từng step khi CI fail.

## Lỗi thường gặp

**Dùng `npm install` trong CI.** Lệnh này có thể resolve lại cây dependency. Trong CI dùng `npm ci`.

**Gọi action không có version, ví dụ `uses: actions/checkout`.** Lần chạy sau có thể kéo code mới, ngoài bản đã review. Gắn tag đã chọn. Muốn chặt hơn thì pin đủ commit SHA, theo [Secure use reference](https://docs.github.com/en/actions/reference/security/secure-use).

**Nhét connection string vào `run:` cho tiện debug.** Log workflow lưu cả lệnh lẫn output. Credential đi qua secret đã cấu hình, và đừng in ra log. Mẫu CI này chưa cần credential.

**Checkout code từ pull request của fork trong workflow có secret.** Trigger nhạy như `pull_request_target` có phần riêng trong tài liệu secure use. Workflow lab chỉ dùng `push` và `pull_request`.

## Nguồn

1. [Workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) — GitHub, `on`, `jobs`, `permissions`, `uses`.
2. [Secure use reference](https://docs.github.com/en/actions/reference/security/secure-use) — permission tối thiểu, pin action, trigger nhạy.
3. [actions/checkout](https://github.com/actions/checkout) và [actions/setup-node](https://github.com/actions/setup-node) — README hai action, gồm major version đang phát hành.
4. [npm ci](https://docs.npmjs.com/cli/commands/npm-ci) — cài từ lockfile.
5. [Node.js Test runner](https://nodejs.org/api/test.html) — `node --test`, trong docs Node.js.

## Tiếp theo

[Jenkins](./03-jenkins.md)
