Featured image of post CI/CD với GitHub Actions cho ứng dụng Docker

CI/CD với GitHub Actions cho ứng dụng Docker

Hướng dẫn thiết lập pipeline CI/CD với GitHub Actions để tự động build, test và push Docker image lên Docker Hub và GitHub Container Registry (GHCR).

GitHub Actions + Docker — Tự động hóa từ code đến container

Khi bạn đã biết viết Dockerfile và dùng Docker Compose, bước tiếp theo là tự động hóa quá trình build và push Docker image mỗi khi code thay đổi. GitHub Actions là công cụ CI/CD miễn phí tích hợp sẵn trong GitHub, rất phù hợp để bắt đầu.

Trong bài viết này, mình sẽ hướng dẫn bạn từ cơ bản đến thực hành: tạo workflow build Docker image, push lên registry, và tối ưu với cache.


I. GitHub Actions là gì?

GitHub Actions là nền tảng CI/CD được tích hợp trực tiếp trong GitHub, cho phép bạn tự động hóa workflow phát triển phần mềm ngay trong repository.

1. Các khái niệm cơ bản

Khái niệmGiải thích
WorkflowFile YAML định nghĩa pipeline, lưu trong .github/workflows/
EventSự kiện kích hoạt workflow (push, pull_request, tag…)
JobMột nhóm các bước (steps) chạy trên cùng một runner
StepMột bước đơn lẻ trong job (chạy lệnh hoặc dùng action)
ActionThành phần tái sử dụng, có thể do cộng đồng hoặc Docker cung cấp
RunnerMáy ảo thực thi workflow (GitHub cung cấp miễn phí Ubuntu, Windows, macOS)

2. Cấu trúc thư mục

1
2
3
4
5
6
7
my-project/
├── .github/
│   └── workflows/
│       └── docker-build.yml    # Workflow file
├── Dockerfile
├── src/
└── ...

II. Docker Official Actions

Docker cung cấp bộ Actions chính thức giúp bạn build và push image một cách chuẩn hóa:

ActionChức năng
docker/login-actionĐăng nhập vào Docker registry
docker/setup-buildx-actionThiết lập Docker Buildx (hỗ trợ build nâng cao)
docker/build-push-actionBuild và push Docker image
docker/metadata-actionTự động tạo tags và labels từ Git metadata
docker/setup-qemu-actionCài QEMU cho multi-platform build

III. Workflow cơ bản — Build và Push lên Docker Hub

1. Chuẩn bị secrets

Trước khi tạo workflow, bạn cần thêm secrets trong repo GitHub:

  1. Vào Settings → Secrets and variables → Actions
  2. Thêm 2 secrets:

2. Tạo workflow file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# .github/workflows/docker-build.yml
name: Build and Push Docker Image

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

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest

3. Giải thích từng phần

PhầnÝ nghĩa
on: push: branches: [main]Chạy workflow khi push code lên branch main
on: pull_requestChạy khi tạo PR (chỉ build, không push)
actions/checkout@v4Clone code từ repo về runner
docker/login-action@v3Đăng nhập Docker Hub bằng secrets
docker/setup-buildx-action@v3Cài Docker Buildx — hỗ trợ cache, multi-platform
docker/build-push-action@v6Build image từ Dockerfile và push lên registry
push: ${{ github.event_name != 'pull_request' }}Chỉ push image khi không phải PR (tránh push image chưa review)

IV. Push lên GitHub Container Registry (GHCR)

Ngoài Docker Hub, bạn có thể push image lên GHCR — registry miễn phí của GitHub, tiện lợi vì không cần tạo tài khoản bên ngoài.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# .github/workflows/docker-ghcr.yml
name: Build and Push to GHCR

on:
  push:
    branches: [main]
    tags: ["v*"]

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Điểm khác biệt so với Docker Hub

Tiêu chíDocker HubGHCR
Registry URLdocker.io (mặc định)ghcr.io
AuthenticationUsername + Access TokenGITHUB_TOKEN (tự động)
GiáMiễn phí (giới hạn pull)Miễn phí (theo quota GitHub)
Tích hợpPhổ biến nhấtTích hợp sâu với GitHub

V. Tự động tạo tags với Metadata Action

Thay vì đặt tag thủ công (latest, v1.0), bạn có thể dùng docker/metadata-action để tự động tạo tags dựa trên Git events:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- name: Extract metadata
  id: meta
  uses: docker/metadata-action@v5
  with:
    images: user/myapp
    tags: |
      type=ref,event=branch
      type=ref,event=pr
      type=semver,pattern={{version}}
      type=semver,pattern={{major}}.{{minor}}
      type=sha

Kết quả tags theo từng event

Git EventVí dụDocker Tag
Push branch mainrefs/heads/mainmain
Push branch devrefs/heads/devdev
Pull Request #42refs/pull/42/mergepr-42
Push tag v1.2.3refs/tags/v1.2.31.2.3, 1.2
Commit SHAabc1234sha-abc1234

VI. Tối ưu build với Cache

Mỗi lần workflow chạy, Docker phải build lại từ đầu. Để tăng tốc đáng kể, hãy sử dụng cache.

1. GitHub Actions Cache (Khuyến nghị)

1
2
3
4
5
6
7
8
- name: Build and push
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: user/myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

Ưu điểm: Đơn giản nhất, sử dụng GitHub Cache API, không cần cấu hình thêm.

2. Registry Cache

1
2
3
4
5
6
7
8
- name: Build and push
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: user/myapp:latest
    cache-from: type=registry,ref=user/myapp:buildcache
    cache-to: type=registry,ref=user/myapp:buildcache,mode=max

Ưu điểm: Cache được lưu trên registry, có thể chia sẻ giữa nhiều CI runners.

So sánh các loại cache

Loại cacheƯu điểmNhược điểm
GitHub Actions (gha)Đơn giản, không cần setup thêmGiới hạn 10GB/repo
RegistryChia sẻ được, persistentTốn bandwidth upload
InlineGắn liền với imageChỉ hỗ trợ min mode

VII. Ví dụ thực tế — Workflow hoàn chỉnh

Dưới đây là workflow hoàn chỉnh kết hợp tất cả best practices:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# .github/workflows/docker-ci.yml
name: Docker CI/CD

on:
  push:
    branches: [main, dev]
    tags: ["v*.*.*"]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

permissions:
  contents: read
  packages: write

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Luồng hoạt động

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Developer push code lên GitHub
GitHub Actions tự động kích hoạt
Checkout code → Setup Buildx → Login Registry
Tự động tạo tags từ Git metadata
Build Docker image (có cache)
Push image lên GHCR (nếu không phải PR)
Image sẵn sàng để deploy 🚀

VIII. Troubleshooting phổ biến

1. Lỗi authentication

Lỗi:

1
Error: denied: denied

Nguyên nhân: Secrets chưa được cấu hình hoặc token hết hạn.

Giải pháp:

  • Kiểm tra secrets trong Settings → Secrets and variables → Actions
  • Với GHCR: đảm bảo có permissions: packages: write trong workflow
  • Với Docker Hub: tạo lại Access Token tại hub.docker.com

2. Build chậm

Nguyên nhân: Không sử dụng cache hoặc Dockerfile chưa tối ưu layer.

Giải pháp:

1
2
3
# Thêm cache vào build step
cache-from: type=gha
cache-to: type=gha,mode=max

Và đảm bảo Dockerfile tận dụng layer caching (copy package.json trước, COPY . . sau).

3. Lỗi permission khi push lên GHCR

Lỗi:

1
Error: unexpected status from HEAD request: 403 Forbidden

Giải pháp: Thêm permissions vào workflow:

1
2
3
permissions:
  contents: read
  packages: write

Ghi chú triển khai

  • Khi áp dụng vào dự án của bạn, hãy lưu ý các điểm quan trọng:
    • Luôn dùng secrets cho credentials, không bao giờ hardcode token trong workflow
    • Sử dụng push: ${{ github.event_name != 'pull_request' }} để tránh push image từ PR chưa review
    • Pin version cho actions (dùng @v3, @v6 thay vì @main)
  • Best practices:
    • Sử dụng docker/metadata-action để quản lý tags tự động
    • Bật cache (type=gha) để tăng tốc build
    • Tách workflow cho build + testdeploy riêng biệt
  • Troubleshooting:
    • Xem logs chi tiết trong tab Actions của repo GitHub
    • Dùng docker compose config để validate cấu hình trước khi push
    • Kiểm tra quota cache GitHub tại Settings → Actions → Cache

🎯 Lời kết

GitHub Actions + Docker là combo mạnh mẽ để tự động hóa pipeline từ code đến container image. Với các workflow trong bài viết này, bạn có thể:

  • ✅ Tự động build Docker image mỗi khi push code
  • ✅ Push image lên Docker Hub hoặc GHCR
  • ✅ Tự động tạo tags theo Git metadata (branch, tag, SHA)
  • ✅ Tối ưu tốc độ build với cache

Nếu bạn thấy bài viết này hữu ích, hãy nhớ ⭐ star repo hoặc 📱 chia sẻ với bạn bè nhé! 😊


Tài liệu tham khảo