Tập 16 đã đóng gói ứng dụng quản lý chi tiêu thành image Docker chạy non-root kèm healthcheck. Tập này nối tiếp đúng điểm đó: tự động hóa đường đi từ lúc push code tới lúc code chạy trên server. Mỗi lần push lên nhánh main, GitHub Actions chạy test trước, rồi deploy tự động — chạy migrations, collectstatic và restart service. Kết quả là một lần push thành một lần deploy, không còn thao tác tay trên server, và test lỗi sẽ chặn deploy trước khi chạm tới production.

Pipeline gồm những bước nào?

Một pipeline deploy chuẩn cho app Django gồm bốn bước nối tiếp nhau. Bước test chạy pytest và kiểm tra migrations còn thiếu. Bước deploy kéo code mới về server, build lại image Docker, rồi chạy migrations, collectstatic và restart. Bước kiểm tra cuối gọi endpoint /health — tập 8 đã xây — để xác nhận app mới đang chạy trước khi coi deploy là xong.

  • Test: pytest, ruff, makemigrations –check –dry-run.
  • Build: docker compose build image mới từ code vừa pull về.
  • Deploy: SSH vào server, chạy migrations, collectstatic rồi restart container.
  • Verify: curl /health/ và kiểm tra log 5 phút đầu.

Viết workflow test và deploy

File .github/workflows/deploy.yml dưới đây chạy trên mỗi push lên main. Job test dùng Python 3.15 và PostgreSQL service container, đúng stack production đã chọn ở tập 4. Chỉ khi test xanh, job deploy mới chạy — dùng appleboy/ssh-action để SSH vào server và gọi script deploy.

name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: expense_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/expense_test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.15"
- run: pip install -r requirements.txt
- run: python manage.py makemigrations --check --dry-run
- run: python manage.py test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: bash ~/deploy.sh

Script deploy trên server

Script deploy.sh nằm trên server, chỉ làm bốn việc theo đúng thứ tự: kéo image mới, chạy migrations, collectstatic rồi restart container. Migrations chạy trước khi app mới nhận request — đúng nguyên tắc tương thích ngược của tập 6, nên deploy không downtime.

#!/bin/bash
set -e
cd ~/expense-app
git pull origin main
docker compose build web
docker compose run --rm web python manage.py migrate --noinput
docker compose run --rm web python manage.py collectstatic --noinput
docker compose up -d web
curl -fsS http://localhost:8000/health/

Ba bẫy thường gặp

Ba lỗi hay gặp nhất khi mới dựng CI/CD đều có thể tránh bằng một dòng cấu hình.

  • Quên –noinput ở collectstatic: lệnh dừng chờ nhập tay và job treo tới khi timeout. Luôn thêm –noinput.
  • Secrets lộ trong log: đừng echo biến môi trường hay in file .env. GitHub tự che secret đã đăng ký, nhưng chỉ khi giá trị khớp chính xác.
  • Deploy cả khi test đỏ: job deploy phải có needs: test, nếu không hai job chạy song song và code lỗi vẫn lên production.

Áp dụng vào dự án của bạn

Với ứng dụng quản lý chi tiêu, hãy bắt đầu từ pipeline tối thiểu: chỉ test và deploy, chưa cần build image riêng nếu server đang chạy code từ git. Tạo một workflow chạy pytest trên mỗi pull request trước — đội của bạn sẽ thấy giá trị ngay khi một test đỏ chặn được một lần deploy lỗi. Khi pipeline đã ổn định, thêm bước build image và health check như trên, rồi mới nghĩ tới blue-green ở tập 18.

Bài viết liên quan

Câu hỏi thường gặp

Test fail thì deploy có chạy không?

Không. Job deploy có needs: test nên GitHub Actions bỏ qua deploy khi test đỏ. Code lỗi dừng ở bước test, production không bị chạm tới — đó chính là giá trị lớn nhất của pipeline này.

Migrations chạy lúc nào trong deploy?

Migrations chạy trên server sau khi kéo image mới và trước khi restart container. Thứ tự này giữ app cũ chạy được với schema mới trong lúc chuyển đổi, đúng mẫu tương thích ngược ở tập 6. Tuyệt đối không chạy migrations trên máy local rồi commit file db.sqlite3.

Có cần build image mới mỗi lần deploy không?

Có, nếu server chạy bằng Docker như tập 16. Mỗi lần deploy build lại image từ code mới nhất để biết chính xác code nào đang chạy, và giữ lại image cũ để rollback bằng cách chạy lại nó. Nếu server chạy code trực tiếp từ git thì chỉ cần git pull rồi restart, nhưng nên chuyển sang image để deploy nhất quán.

Nguồn tham khảo