Git workflow cho team 3–4 người, 3 môi trường
Tài liệu chọn git workflow (quy trình dùng branch/tag để quản lý code) cho bối cảnh: team 3–4 dev, 3 môi trường test → staging → production chạy trên hạ tầng riêng biệt, production deploy bằng blue-green, test và staging deploy bằng recreate.
Phần 1–4 là workflow đề xuất. Phần 5 so sánh với cách "mỗi môi trường là một branch" — cách phổ biến nhất ở team nhỏ và cũng là cách gây đau nhất về sau.
Nền tảng chiến lược deploy: chien-luoc-deployment.md.
Pipeline và deploy.sh cụ thể: laravel-gitlab-cicd.md.
Giả định: mỗi môi trường có hạ tầng riêng, không dùng chung database. Git workflow không phụ thuộc vào việc mỗi môi trường là 1 server hay 3 server — chỗ đó chỉ đổi
deploy.sh, không đổi cách dùng branch.
Từ điển nhanh
| Từ | Nghĩa |
|---|---|
| trunk | Nhánh chính duy nhất mà mọi người merge vào (main) |
| trunk-based development | Kiểu làm việc: branch phụ sống ngắn, merge về trunk liên tục |
| artifact | Sản phẩm build ra được (ở đây: Docker image), thứ thật sự chạy trên server |
| promote | Đẩy đúng một artifact lên môi trường cao hơn, không build lại |
| drift | Các branch lệch nội dung nhau dần theo thời gian |
| cherry-pick | Bê một commit lẻ từ branch này sang branch khác (tạo commit mới, hash khác) |
| fast-forward | Merge không tạo commit mới, chỉ tua con trỏ branch tới trước |
| protected branch | Branch bị GitLab chặn push thẳng, bắt buộc qua merge request |
1. Workflow đề xuất: trunk-based + promote artifact bằng tag
1.1 Branch
| Branch | Ai tạo | Sống bao lâu | Ghi chú |
|---|---|---|---|
main |
— | vĩnh viễn | Trunk. Protected. Luôn ở trạng thái deploy được. |
feature/<ticket>-<mô-tả> |
dev | 1–3 ngày | Merge request (MR) + 1 review, merge xong xoá |
fix/<ticket>-<mô-tả> |
dev | < 1 ngày | Như trên |
hotfix/<ticket> |
dev | vài giờ | Chỉ khi production đang lỗi. Cắt từ tag đang chạy prod. |
Hết. Không có develop, không có release/*, không có branch riêng cho từng
môi trường.
Vì sao branch phải sống ngắn: branch sống 2 tuần thì lúc merge, code đã lệch
xa main → conflict lớn, review khó, và bug chỉ lộ ra ở phút cuối. Với 3–4
người, quy tắc thực dụng là: branch nào quá 3 ngày thì chia nhỏ task, hoặc merge
phần đã xong vào main sau feature flag (cờ bật/tắt tính năng — Laravel Pennant)
để code có mặt trên main nhưng chưa bật cho user.
1.2 Luồng chính
flowchart LR F["feature/x"] --> M["main"] X["fix/y"] --> M M -->|"tự động"| T["TEST<br/>recreate"] M -->|"bấm nút thủ công"| S["STAGING<br/>recreate"] M -->|"tag v1.2.3 + approve"| P["PROD<br/>blue-green"] H["hotfix/z<br/>cắt từ tag v1.2.2"] --> M P -.->|"tag v1.2.4"| P
1.3 Quy tắc quan trọng nhất: build một lần, promote cùng một artifact
Khi merge vào main, CI build Docker image một lần và gắn tag image bằng git
SHA (mã băm của commit), ví dụ registry/app:a1b2c3d.
Test, staging, production đều chạy đúng image đó. Khác nhau chỉ nằm ở file
.env trên từng server (DB host, APP_URL, key của dịch vụ ngoài).
Không build lại ở mỗi môi trường. Build lại nghĩa là mỗi môi trường chạy một binary khác nhau — dependency có thể đã đổi version, asset build khác, và bug kiểu "chỉ xảy ra trên production" sinh ra từ đây. Đây cũng là điều kiện để câu "đã test kỹ trên staging rồi" có nghĩa.
Hệ quả thực tế: từ docker ps trên bất kỳ server nào, đọc tag image là biết
chính xác commit nào đang chạy — không cần đoán, không cần so branch.
1.4 Từ commit tới production — ai bấm gì
| Môi trường | Trigger | Ai | Cách deploy | Rollback |
|---|---|---|---|---|
| test | merge vào main → chạy tự động |
không ai | recreate | deploy lại image SHA cũ |
| staging | job thủ công trên pipeline của một commit main |
dev | recreate | như trên |
| production | push tag v* + manual approval |
lead / PO | blue-green | switch Nginx về màu cũ (giây) |
Tag v1.2.3 chỉ là cái nhãn dán lên commit đã qua staging. Nó không tạo code
mới, không đổi nội dung — nên không có chỗ nào để code "lệch" đi.
Rollback production không đụng tới git: blue-green vẫn giữ màu cũ đang chạy, đổi một dòng config Nginx rồi reload là xong (xem laravel-gitlab-cicd.md mục 4). Git chỉ dùng để tạo bản vá tiếp theo.
2. Cấu hình GitLab CI khớp workflow này
Mở rộng từ .gitlab-ci.yml ở laravel-gitlab-cicd.md mục 2:
stages: [test, build, deploy]
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
test:
stage: test
script:
- composer install --no-interaction
- php artisan test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
build:
stage: build
image: docker:24
services: [docker:24-dind]
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH == "main" # CHỈ build ở main — một commit, một image
.deploy_template: &deploy
stage: deploy
image: alpine:3.20
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
- mkdir -p ~/.ssh && ssh-keyscan -H $SERVER_HOST >> ~/.ssh/known_hosts
deploy:test:
<<: *deploy
script:
- ssh $SERVER_USER@$TEST_HOST "/srv/app/deploy.sh $IMAGE_TAG"
environment:
name: test
rules:
- if: $CI_COMMIT_BRANCH == "main" # tự động, không cần bấm
deploy:staging:
<<: *deploy
script:
- ssh $SERVER_USER@$STAGING_HOST "/srv/app/deploy.sh $IMAGE_TAG"
environment:
name: staging
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual # dev tự bấm khi muốn PO xem
deploy:production:
<<: *deploy
script:
# Image phải đã tồn tại — chặn trường hợp tag nhầm vào commit chưa qua CI.
- docker manifest inspect $IMAGE_TAG > /dev/null
- ssh $SERVER_USER@$PROD_HOST "/srv/app/deploy.sh $IMAGE_TAG"
environment:
name: production
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual # + Protected environment: chỉ lead approve được
Vài chỗ đáng chú ý:
buildchỉ chạy ởmain. Pipeline của tag không build lại — nó dùng luôn image đã có, vì tag trỏ vào đúng commit đó nên$CI_COMMIT_SHORT_SHAgiống hệt. Đây chính là "build một lần" được cưỡng chế bằng cấu hình.docker manifest inspectlà cái chốt: nếu ai đó tag một commit chưa từng qua CI, job fail ngay với lý do rõ ràng thay vì deploy image cũ hoặc image rác.- Protected environment (Settings → CI/CD → Protected environments): đặt
productionchỉ cho role Maintainer bấm. Nút manual thôi thì chưa đủ — ai cũng bấm được. - Protected branch
main: chặn push thẳng, bắt buộc qua MR. Đây là hàng rào duy nhất thật sự cần thiết cho team 3–4 người.
3. Hotfix production
flowchart LR TAG["tag v1.2.2<br/>(đang chạy prod)"] --> HF["hotfix/PAY-91"] HF --> MR["MR vào main"] --> MAIN["main"] MAIN -->|"auto"| TEST["TEST"] MAIN -->|"bấm nút"| STG["STAGING<br/>smoke test nhanh"] MAIN -->|"tag v1.2.3"| PROD["PROD"]
Quan trọng: hotfix cắt từ tag đang chạy prod, không cắt từ main. Vì main
lúc đó có thể đã chứa tính năng chưa được duyệt — nếu cắt từ main, bản vá sẽ
kéo theo cả những thứ đó lên production.
Nhưng sau khi vá xong thì merge vào main, không merge thẳng lên production.
Chỉ có một đường vào production: qua main rồi tag. Không có ngoại lệ, kể cả lúc
3 giờ sáng — ngoại lệ là chỗ code bị mất ở lần deploy kế tiếp.
Nếu main đã đi quá xa và hotfix conflict nặng khi merge ngược: đó là dấu hiệu
đang giữ code chưa release quá lâu trên main, không phải dấu hiệu cần thêm
branch. Cách chữa là release nhỏ và thường xuyên hơn.
4. Vì sao không dùng GitFlow
GitFlow có 5 loại branch (main, develop, feature/*, release/*,
hotfix/*) và quy tắc merge hai chiều giữa chúng. Nó được thiết kế cho phần mềm
phát hành theo phiên bản đóng gói (desktop app, thư viện, sản phẩm có nhiều
version chạy song song ở khách hàng).
Với web app deploy liên tục lên hạ tầng mình tự quản, develop và release/*
chỉ nhân đôi việc merge mà không thêm an toàn nào — cái an toàn đó đã nằm ở
staging + manual approval rồi. Team 3–4 người mà nuôi 5 loại branch thì chi phí
merge lớn hơn giá trị thu được.
5. So sánh: mỗi môi trường một branch
Đây là cách người dùng hay nghĩ tới đầu tiên, nên phần này đi kỹ.
5.1 Cách đó hoạt động thế nào
Ba branch dài hạn, mỗi branch gắn cứng với một môi trường:
flowchart LR F["feature/x"] --> T["branch test"] --> S["branch staging"] --> P["branch production"] T --> ST["server TEST"] S --> SS["server STAGING"] P --> SP["server PROD"]
Deploy = merge lên branch kế tiếp. CI chỉ cần rule đơn giản: push vào staging
thì deploy staging, push vào production thì deploy production.
5.2 Ưu điểm (có thật, không nên bỏ qua)
- Trực quan. Muốn biết staging đang có gì thì mở branch
stagingra xem. Với người mới vào team, mô hình này dễ hiểu hơn tag. - Deploy không cần quyền CI. Merge được là deploy được. Không phải học tag, không phải tìm nút manual trong pipeline.
- Hợp khi deploy không có artifact. Nếu deploy là
git pulltrên server (không Docker, không registry), branch chính là thứ duy nhất mô tả được cái gì đang chạy — lúc đó env-branch là lựa chọn đúng. - CI config ngắn nhất. Chỉ cần
if: $CI_COMMIT_BRANCH == "staging".
5.3 Nhược điểm — theo thứ tự mức độ đau
a. Drift: ba branch lệch nhau dần
Hotfix vá thẳng lên production cho nhanh. Ai đó quên merge ngược về staging
và test.
Bản thân git merge staging không làm mất bản vá. Merge là 3-way merge (so
cả hai nhánh với merge-base — commit tổ tiên chung), nên commit hotfix đã nằm
trong lịch sử production được giữ lại; nếu staging sửa đúng dòng đó thì git
báo CONFLICT và dừng, chứ không im lặng ghi đè. Git còn theo được cả rename:
staging đổi tên Pay.php → PaymentService.php thì hotfix tự chuyển sang file
mới. Nguy hiểm nằm ở chỗ khác:
- Giải conflict sai bên. Thấy conflict, chọn "lấy bản mới nhất cho nhanh".
Bản tự động của nó là
git merge -X theirs staging— exit 0, không cảnh báo, bản vá biến mất. - Promote bằng cách thay cả cây code, không phải merge.
git reset --hard staging,git checkout staging -- ., force-push, hoặc script deploy chạygit checkout staging && git pullngay trên server prod. Cả ba nuốt hotfix, exit 0, pipeline xanh. - Promote bằng cherry-pick. Lúc đó
productionkhông có merge nào cả — release là tập commit chọn tay, và hotfix không nằm trong danh sách chọn. - Image release build từ commit của
stagingrồi promote (build once, deploy many). Hotfix ở branchproductionchưa bao giờ vào artifact — merge có đúng cũng vô ích, vì thứ chạy trên prod là image kia. - Semantic drift — kiểu duy nhất git không thấy được. Staging thêm một đường gọi mới đi vòng qua hàm đã vá, hoặc viết lại logic ở chỗ khác. Merge sạch, test xanh, bug vẫn quay lại. Git merge text, không hiểu ý nghĩa.
Và thiệt hại chắc chắn xảy ra kể cả khi mọi thứ đúng: test và staging không
có bản vá. QA gặp lại bug, dev vá lần hai theo cách khác → hai bản vá khác nhau
cho cùng một bug, và lần merge sau conflict nặng.
Đây là nhóm lỗi khó phát hiện, vì phần lớn không có gì báo lỗi cả: merge thành công, pipeline xanh, chỉ có bug lặng lẽ tái xuất hiện. Với trunk + tag, cả nhóm này không thể xảy ra vì chỉ có một branch chứa code.
b. Cherry-pick phá vỡ ý nghĩa của việc test
PO chỉ duyệt tính năng A, nhưng test đang có A + B + C. Muốn đưa riêng A lên
staging thì phải cherry-pick.
Cherry-pick tạo commit mới với hash khác. Nghĩa là cây code trên staging
không còn giống cây code đã test trên test — nó là một tổ hợp chưa ai chạy bao
giờ. Câu "đã test rồi" mất giá trị đúng lúc cần nó nhất.
Càng làm nhiều cherry-pick, ba branch càng lệch, và conflict lúc merge càng nặng — vì git thấy cùng một thay đổi ở hai hash khác nhau.
c. Build lại ở mỗi môi trường — mâu thuẫn trực tiếp với setup blue-green
Mỗi branch push là một pipeline, một image mới. Ba branch → ba image build từ ba
cây source khác nhau, ba thời điểm khác nhau (composer install giữa hai lần
build có thể kéo patch version khác nếu lock file không chặt tuyệt đối).
Bạn đang có Docker registry và blue-green — tức là đã có sẵn artifact có thể promote. Env-branch vứt bỏ lợi thế đó và quay lại kiểu "mỗi môi trường một bản build riêng".
d. Cùng một conflict giải ba lần
Merge test → staging conflict, giải. Merge staging → production conflict
tương tự, giải lại. Mỗi lần giải tay là một cơ hội giải sai, và lần giải sai đó
chỉ lộ ra ở môi trường cuối cùng.
e. Không trả lời chắc chắn được "production đang chạy commit nào"
Branch là con trỏ di động — nó bị đẩy tới lui, force-push, revert. Tag thì bất
biến. Lúc sự cố, câu hỏi đầu tiên luôn là "bản đang chạy là bản nào", và
git log --oneline production không đáng tin bằng tag v1.2.3 + image SHA đang
chạy trong docker ps.
f. Rollback chậm hơn
Env-branch coi rollback là thao tác git: revert commit, push, chờ pipeline build + deploy — vài phút, trong lúc production đang lỗi. Blue-green rollback là switch Nginx: vài giây, và màu cũ vẫn đang chạy sẵn.
5.4 Bảng so sánh
| Tiêu chí | Trunk + tag (đề xuất) | Mỗi env một branch |
|---|---|---|
| Số branch dài hạn | 1 | 3 |
| Code chạy trên 3 env | Cùng một artifact | Ba lần build khác nhau |
| Nguy cơ drift giữa các env | Không có (một branch) | Cao, tăng theo thời gian |
| Release chọn lọc từng tính năng | Feature flag | Cherry-pick (dễ sai) |
| Biết prod đang chạy gì | Tag + image SHA, bất biến | Đọc branch, con trỏ di động |
| Rollback prod | Switch màu blue-green, vài giây | Revert + build + deploy, vài phút |
| Merge overhead | 1 lần (MR vào main) | 3 lần + conflict lặp |
| Độ dễ hiểu cho người mới | Trung bình (phải hiểu tag) | Cao |
Hợp khi deploy bằng git pull |
Không | Có |
| Hợp khi có Docker registry | Có | Lãng phí |
5.5 Khi nào env-branch vẫn là lựa chọn đúng
Không phải lúc nào nó cũng sai. Nó đúng khi:
- Deploy bằng
git pulltrên server, không có CI/CD và không có registry. - Các môi trường cố ý chạy code khác nhau lâu dài — ví dụ giữ một phiên bản riêng cho một khách hàng lớn. Nhưng đó là release branch (branch theo phiên bản), không phải environment branch, và nó có vòng đời riêng.
- Team không có ai được giao quyền duyệt deploy, nên phải mượn quyền merge làm cơ chế kiểm soát.
Bối cảnh của bạn — Docker image, GitLab CI/CD, blue-green — không rơi vào ba trường hợp này.
5.6 Biến thể lai: main + một branch production chỉ để ghi lại
Có team giữ main làm trunk, thêm branch production chỉ được fast-forward tới
commit đang chạy prod, để "nhìn phát biết ngay".
Cách này tránh được drift (vì fast-forward only, không bao giờ có commit riêng), nhưng nó chỉ làm lại đúng việc mà tag đã làm — và thêm một con trỏ nữa phải nhớ cập nhật. Nếu muốn cái nhìn nhanh "prod đang chạy gì", dùng GitLab Environments: nó hiển thị sẵn commit + thời điểm deploy của từng môi trường.
6. Ràng buộc kèm theo workflow này
- Migration DB phải backward-compatible. Blue-green có hai version code sống cùng lúc trên cùng một database lúc chuyển màu — bắt buộc expand-contract (thêm cột mới trước, xoá cột cũ ở release sau). Chi tiết: migration-postgresql-zero-downtime.md.
mainphải luôn xanh. Trunk-based chỉ hoạt động nếu test chạy ở MR và không ai merge khi đỏ. Bật "Pipelines must succeed" trong Merge request settings.- Tính năng chưa xong thì giấu sau feature flag, đừng giấu bằng branch sống lâu. Đây là cách trunk-based đánh đổi: chuyển độ phức tạp từ git sang code, chỗ mà nó test được.
- Semantic versioning cho tag:
v<major>.<minor>.<patch>— patch cho hotfix, minor cho tính năng mới, major cho breaking change. Với web app nội bộ thì minor/patch là đủ dùng.
7. Case thực tế có thể gặp và cách xử lý
Case 1 — Production đang lỗi, nhưng main đã có 3 tính năng chưa release.
Không được deploy main để sửa 1 dòng.
Xử lý: git checkout -b hotfix/x v1.4.2 (cắt từ tag đang chạy prod, không
phải từ main), sửa, tag v1.4.3, deploy. Xong thì merge hotfix về main
ngay trong ngày — quên bước này là bug quay lại ở release sau.
Ngăn lần sau: để bước "merge back" thành một MR bắt buộc trong quy trình
hotfix, đừng để nó là việc ai đó nhớ thì làm.
Case 2 — Feature branch sống 2 tuần, merge ra 40 file conflict.
Xử lý ngay: rebase lên main theo từng chặng, không giải một lần; nếu quá
rối thì bỏ branch, chia lại thành 3 MR nhỏ dựa trên main mới.
Ngăn lần sau: branch sống tối đa 1–2 ngày. Phần chưa xong giấu sau feature
flag rồi merge vào main. Trunk-based đổi độ phức tạp từ git (nơi không
test được) sang code (nơi test được).
Case 3 — Rollback prod về tag cũ, app chạy lại nhưng dữ liệu sai.
Release lỗi đã chạy migration DROP COLUMN và đã ghi vào bảng mới.
Xử lý ngay: rollback code về tag trước là bước 1; bước 2 là đánh giá dữ liệu
đã ghi sai — thường phải viết script sửa, không có nút undo.
Ngăn lần sau: release có DROP/RENAME phải tách 2 lần deploy. Git rollback
chỉ nhanh khi schema còn tương thích với code cũ.
Case 4 — Một MR đã merge vào main bị phát hiện sai sau khi lên test.
Xử lý: git revert <merge-commit> -m 1 rồi merge như MR bình thường. Không
reset --hard main (nó là protected branch, và người khác đã pull rồi). Sửa
xong thì mở MR mới, revert lại cái revert.
Case 5 — Staging xanh, production fail ngay khi deploy.
Nguyên nhân gần như luôn là: hai môi trường chạy hai artifact khác nhau
(build lại theo từng môi trường), hoặc thiếu biến env ở prod.
Xử lý ngay: rollback về tag trước, so docker image digest giữa staging và
prod, so danh sách biến env.
Ngăn lần sau: build đúng một image trên main, promote cùng digest đó
qua các môi trường bằng tag. Khác biệt duy nhất giữa môi trường là biến env
lúc runtime.
Case 6 — Ai đó push thẳng vào main (hoặc force-push) làm mất commit.
Xử lý ngay: git reflog trên máy người đó hoặc git fsck trên server để tìm
commit mồ côi, tạo branch trỏ lại.
Ngăn lần sau: main là protected branch — chặn push thẳng, chặn force push.
Đây là cấu hình 30 giây, làm ngay ngày đầu.
Case 7 — Deploy nhầm tag cũ lên production. Xử lý ngay: deploy lại đúng tag. Nguy hiểm thật nằm ở migration: tag cũ + schema mới có chạy được không? Nếu migration là expand-only thì có. Ngăn lần sau: job deploy prod là manual + cần approve, và in rõ tag + digest ra log trước khi chạy.
Case 8 — Feature flag đẻ ra rác.
Sau 6 tháng còn 20 flag, không ai biết cái nào còn dùng, code đầy if.
Xử lý: mỗi flag có chủ và hạn xoá (ghi trong MR tạo ra nó). Flag đã bật 100%
qua 2 release thì xoá cả flag lẫn nhánh code chết.
8. Câu hỏi phụ interviewer hay hỏi tiếp
"Trunk-based thì code chưa xong cũng nằm trên main à, không sợ lên prod?"
Code chưa xong lên main nhưng tắt bằng feature flag, nên nó không chạy với
user. Đổi lại: nó được build, test và merge liên tục, thay vì nằm một mình
trong branch 2 tuần rồi vỡ lúc merge.
"Không có branch develop thì QA test ở đâu?"
Môi trường test deploy tự động từ mọi commit trên main. staging deploy từ
tag ứng viên (release candidate). Môi trường thay vai trò của branch — và môi
trường thì chạy được, còn branch thì không.
"Team 3 người có cần review MR không, hay chậm quá?" Cần, nhưng nhẹ: 1 approval, MR nhỏ (dưới ~400 dòng), review trong ngày. Giá trị lớn nhất không phải bắt bug mà là chia sẻ hiểu biết — team 3 người mà 1 người nghỉ thì 2 người còn lại phải hiểu code đó.
"Dùng tag hay branch để đánh dấu cái đang chạy trên prod?"
Tag. Branch là con trỏ thay đổi được — hôm nay production trỏ commit A,
mai trỏ B, không tra lại được. Tag v1.4.2 (protected tag) là bất biến, map
1-1 với một image digest.
"GitFlow sai ở đâu mà nhiều người bỏ?"
GitFlow thiết kế cho phần mềm phát hành nhiều version song song và phải hỗ trợ
version cũ (app desktop, thư viện). Web app chỉ có một version đang chạy —
develop + release/* chỉ thêm bước merge mà không thêm thông tin gì.
"Squash merge hay merge commit?"
Squash cho feature branch: main có lịch sử sạch, mỗi commit = một thay đổi
hoàn chỉnh, git revert một phát là gọn. Merge commit chỉ giữ khi cần lịch sử
chi tiết của branch đó (hiếm với branch sống 1–2 ngày).
"Nếu công ty bắt release theo lịch 2 tuần một lần thì sao?"
Vẫn trunk-based, thêm branch release/1.5 ngắn hạn cắt từ main khi đóng
sổ; chỉ nhận bug fix, không nhận tính năng; tag từ đó. Branch này sống vài
ngày rồi xoá, khác hẳn branch môi trường sống mãi mãi.
"Build một lần thì làm sao mỗi môi trường có cấu hình khác nhau?" Cấu hình đi vào biến env lúc runtime, không build vào image. Image chỉ chứa code + dependency. Nếu phải build lại mới đổi được cấu hình thì đó là dấu hiệu cấu hình đang bị nhét sai chỗ.
"Ai được deploy production?"
Đặt production là protected environment trong GitLab: job deploy là manual và
cần Maintainer approve. Không phải vì không tin đồng đội, mà để có một chỗ
dừng có chủ ý trước bước không hoàn tác được, và có log ai bấm lúc nào.
Checklist thiết lập lần đầu
-
mainlà protected branch, chặn push thẳng - MR bắt buộc 1 approval + pipeline xanh mới merge được
- Bật "Delete source branch" mặc định khi merge
- Tag
v*là protected tag, chỉ Maintainer tạo được -
productionlà protected environment, chỉ Maintainer approve deploy - Biến CI:
TEST_HOST,STAGING_HOST,PROD_HOST,SSH_PRIVATE_KEY(Masked + Protected) -
deploy.shtrên mỗi server nhận image tag làm tham số (prod dùng bản blue-green)
Tóm tắt để trả lời phỏng vấn
"Với team 3–4 người và 3 môi trường, tôi dùng trunk-based: một
mainprotected, branch tính năng sống 1–2 ngày, build một image trênmainrồi promote chính image đó qua test → staging → production bằng tag. Cái tôi tránh là mỗi môi trường một branch: nó bắt build lại theo từng môi trường (nên thứ test ở staging không phải thứ chạy ở prod), gây drift khi hotfix quên merge ngược, và con trỏ branch thì thay đổi được nên không tra lại được prod đang chạy commit nào. Tính năng chưa xong thì giấu sau feature flag chứ không giấu bằng branch sống lâu, và hotfix luôn cắt từ tag đang chạy production rồi merge ngược vềmainngay trong ngày."