Hướng dẫn này giúp bạn cấu hình Docker và thiết lập workflow CI/CD trên GitHub để:
- Build và push Docker image lên GitHub Container Registry (GHCR)
- Tự động SSH vào server và chạy script
deploy.sh
để làm mới container ( thật ra có thể dùng watchtower để watch thay vì như mình)
1. 🐳 Tạo Dockerfile cho NestJS
# =====================================================
# GIAI ĐOẠN BUILD (BUILD STAGE)
# =====================================================
# Sử dụng Node.js 20 với Alpine Linux làm base image
# Alpine Linux được chọn vì nhẹ và bảo mật tốt
FROM node:20-alpine AS builder
# Thiết lập thư mục làm việc trong container
WORKDIR /app
# Cài đặt các công cụ cần thiết để build
# python3: Cần cho một số package native
# make và g++: Cần cho việc biên dịch các dependencies C++
RUN apk add --no-cache python3 make g++
# Kích hoạt corepack để quản lý yarn
# Corepack là công cụ quản lý package manager của Node.js
RUN corepack enable
# Copy các file quản lý dependencies
# Chỉ copy những file này trước để tận dụng cache layer của Docker
COPY package*.json yarn.lock ./
# Cài đặt tất cả dependencies với cache
# --mount=type=cache: Sử dụng BuildKit để cache node_modules
# --frozen-lockfile: Đảm bảo versions khớp với yarn.lock
# --prefer-offline: Ưu tiên dùng cache thay vì tải lại
RUN --mount=type=cache,target=/root/.yarn/cache \
--mount=type=cache,target=/root/.yarn/berry/cache \
yarn install --frozen-lockfile --prefer-offline
# Copy toàn bộ source code vào container
# .dockerignore sẽ loại bỏ các file không cần thiết
COPY . .
# Build ứng dụng cho production
RUN yarn build:prod
# =====================================================
# GIAI ĐOẠN PRODUCTION (PRODUCTION STAGE)
# =====================================================
# Tạo image mới cho production, giảm kích thước
FROM node:20-alpine AS production
# Thiết lập thư mục làm việc
WORKDIR /app
# Copy các file quản lý dependencies
COPY package*.json yarn.lock ./
# Kích hoạt corepack cho yarn
RUN corepack enable
# Cài đặt chỉ dependencies cho production
# Không cài đặt devDependencies để giảm kích thước
RUN --mount=type=cache,target=/root/.yarn/cache \
--mount=type=cache,target=/root/.yarn/berry/cache \
yarn install --frozen-lockfile --production --prefer-offline
# Copy các file đã build từ stage trước
# Chỉ copy những gì cần thiết cho production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public
COPY --from=builder /app/.env.production ./.env
# Thiết lập biến môi trường
# NODE_ENV=production: Chạy ở chế độ production
# PORT=5005: Port mà ứng dụng sẽ lắng nghe
ENV NODE_ENV=production \
PORT=5005
# Khai báo port sẽ được sử dụng
EXPOSE 5005
# Tạo user không có quyền root để tăng bảo mật
# addgroup: Tạo group mới
# adduser: Tạo user mới và thêm vào group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Lệnh khởi động ứng dụng
# Sử dụng CMD thay vì ENTRYPOINT để có thể override khi cần
CMD ["yarn", "start:prod"]
2. ⚙️ Tạo GitHub Actions Workflow
Tạo file .github/workflows/docker-build.yml
:
# Tên của workflow, sẽ hiển thị trong tab Actions của GitHub
name: Build and Push Docker Image
# Xác định khi nào workflow này sẽ được trigger
on:
push:
branches: ['master'] # Chạy khi có push vào nhánh master
pull_request:
branches: ['master'] # Chạy khi có pull request vào nhánh master
# Định nghĩa các biến môi trường dùng trong workflow
env:
# Địa chỉ registry để push Docker image
REGISTRY: ghcr.io
# Tên image sẽ được tạo, sử dụng tên repository
IMAGE_NAME: ${{ github.repository }}
# Định nghĩa các jobs sẽ được thực thi
jobs:
build-and-push:
# Chọn hệ điều hành để chạy job
runs-on: ubuntu-latest
# Cấp quyền cần thiết cho job
permissions:
contents: read # Quyền đọc code từ repository
packages: write # Quyền ghi vào GitHub Packages (để push image)
# Các bước thực hiện trong job
steps:
# Bước 1: Check out code từ repository
- name: Checkout repository
uses: actions/checkout@v4
# Bước 2: Tạo file .env.production từ GitHub secrets
- name: Create .env.production file
run: |
echo "${{ secrets.ENV_PRODUCTION }}" > .env.production
# Bước 3: Cài đặt Docker Buildx
# Buildx là plugin của Docker cho phép build đa nền tảng và có nhiều tính năng mở rộng
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Bước 4: Đăng nhập vào GitHub Container Registry
# Sử dụng GITHUB_TOKEN tự động được GitHub cung cấp
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.ACCESS_TOKEN }}
# Bước 5: Trích xuất metadata cho Docker image
# Tạo các tags và labels phù hợp dựa trên context của GitHub
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Cấu hình các loại tags sẽ được tạo:
tags: |
type=ref,event=branch # Tag theo tên nhánh
type=ref,event=pr # Tag cho pull request
type=semver,pattern={{version}} # Tag theo version (vd: v1.0.0)
type=semver,pattern={{major}}.{{minor}} # Tag theo major.minor (vd: v1.0)
type=sha,format=long # Tag theo SHA của commit
# Bước 6: Build và push Docker image
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: . # Thư mục chứa Dockerfile
push: true # Push image sau khi build
tags: ${{ steps.meta.outputs.tags }} # Sử dụng tags đã tạo ở bước trước
labels: ${{ steps.meta.outputs.labels }} # Sử dụng labels đã tạo ở bước trước
# Sử dụng cache để tối ưu quá trình build
cache-from: type=gha # Lấy cache từ GitHub Actions
cache-to: type=gha,mode=max # Lưu cache lại cho các lần build sau
3. 🔐 Cấu hình Secrets trong GitHub
Vào GitHub > Settings > Secrets and Variables > Actions > Add secrets:
Tên secret | Giá trị |
ACCESS_TOKEN | Personal Access Token có quyền write:packages |
ENV_PRODUCTION | Nội dung file .env.production |