Quy tắc triển khai
Tuyệt đối không deploy từ máy local
Không chạy wrangler deploy hay wrangler pages deploy từ máy cá nhân, kể cả khi build đã sạch và test đã xanh.
Cách triển khai duy nhất: push lên GitHub, để CI deploy.
Đây là quy tắc do chủ sản phẩm đặt ra. Deploy từ local đã từng gây ra bản Preview đè lên Production và tình trạng lệch phiên bản khó truy vết.
Đường đi của một thay đổi
sửa code → build & test tại local → đẩy nhánh, mở PR (ci-checks chạy) → merge vào main → GitHub Actions → CloudflarePush vào nhánh main sẽ kích hoạt workflow tương ứng theo thư mục thay đổi:
| Workflow | Triển khai |
|---|---|
deploy-api.yml | api.conan.school → Worker api-conan-school (áp migration D1 + tạo chỉ mục Vectorize trước) |
deploy-com.yml | tokyo.conan.school → Pages conan-com |
deploy-www.yml | www.conan.school → Pages ww3-conan-school (sau worker bridge) |
deploy-admin.yml · deploy-admin-router.yml | admin.conan.school → Pages admin-conan-school · Worker route |
deploy-docs.yml | docs.conan.school → Pages docs-conan-school |
deploy-mam.yml · deploy-video.yml | Pages mam-conan-school · video-conan-school |
deploy-play.yml | Pages kahoot-frontend + Worker route api.conan.school/play/* |
deploy-mentors.yml | Worker qua vinext deploy (Next, miễn trừ) |
deploy-tool-plane.yml (+ provision-, smoke-test-) | Worker conan-tool-plane → mcp.conan.school |
Mỗi workflow có concurrency riêng (lượt sau xếp hàng, không huỷ lượt đang chạy). Bảng đầy đủ kèm trạng thái và quyền token cần có: .github/workflows/README.md.
Kiểm tra kết quả: gh run list --branch main --limit 10. Đọc cột kết luận, đừng chỉ nhìn trang web còn tải: deploy-api đỏ liên tục 02–03.09.2026 (token thiếu D1 Edit) mà không màn hình nào báo, xem [Sổ rủi ro](/curriculum/known-risks#token-thieu-d1-edit-khong-chi-lam-ci-đo-, -no-đong-bang-ca-deploy-va-khong-ai-đo).
Được phép chạy tại local
Những lệnh sau không phải deploy và được dùng bình thường:
| Lệnh | Việc |
|---|---|
wrangler dev --remote | Chạy code local, nối tài nguyên production. Dùng để kiểm thử và chạy tác vụ dữ liệu |
wrangler deploy --dry-run | Kiểm tra bundle có build được không |
wrangler d1 execute … --remote --command "SELECT …" | Chỉ đọc để chẩn đoán. Ghi schema đi qua migration do CI áp; ghi nội dung đi qua migration trong git (CLAUDE.md) |
npm run build | Build kiểm tra |
Workflows chạy code đã deploy
Cloudflare Workflows luôn chạy bản đã deploy, không phải code đang sửa tại local. Sửa một bước trong workflow rồi test ngay tại local sẽ vẫn thấy hành vi cũ, phải push và đợi CI xong.
Migration và hạ tầng do CI làm
Từ 01.09.2026, CI áp migration D1 và tạo chỉ mục Vectorize trước bước deploy, trong cùng một job (d1_migrate và vectorize_indexes trong _deploy-cloudflare.yml). Không còn bước tay nào.
Điều này không phải là nới luật "áp từng file một", nó là cách thi hành luật đó bằng máy:
scripts/apply-d1-migrations.mjsáp từng file một, theo thứ tự tên, và dừng ngay ở file đầu tiên hỏng. Deploy không chạy tiếp.- Sổ
schema_migrationsghi file nào đã áp, nên không file nào chạy hai lần. - Mốc nền. Lượt chạy đầu tiên không áp gì cả: nó đánh dấu mọi file có tên
<= BASELINE(20260829o_backfill_queue.sql) là "đã áp tay từ trước". Không có mốc nền này, lượt CI đầu tiên sẽ dội 160 file cũ lên production, chính là điều luật kia sinh ra để chặn. Đừng bao giờ lùiBASELINEvề quá khứ: làm vậy là ra lệnh cho CI chạy lại lịch sử.
Thứ tự migration-trước-deploy không đảo được. Deploy mã đọc một bảng chưa tồn tại hỏng theo kiểu tệ nhất: endpoint đọc vẫn xanh (phần lớn câu truy vấn có .catch() trả rỗng) nên màn hình trông bình thường, còn endpoint ghi chết 500 mà không có gì chỉ ra nguyên nhân thật.
Tương tự với Vectorize: một binding trong wrangler.toml trỏ vào chỉ mục chưa tồn tại không làm hỏng một tính năng, nó làm hỏng cả worker khi deploy. Nên bước Ensure Vectorize indexes (tạo idempotent) chạy trước, và chỉ nuốt đúng lỗi "đã tồn tại"; thiếu quyền hay sai tài khoản vẫn làm dừng CI.
Bảng mới vẫn nên dùng CREATE TABLE IF NOT EXISTS để file migration chạy lại được vô hại.
Quyền của token
CLOUDFLARE_API_TOKEN nay cần thêm D1 → Edit và Vectorize → Edit ngoài Workers/Pages. Thiếu quyền thì job dừng ở bước migration với lỗi 403, deploy không chạy, production không đổi.
Kiểm thử trước khi push
Thứ tự tối thiểu:
npx tsc --noEmit, so số lỗi trước và sau khi sửa; dự án còn nhiều lỗi kiểu cũ, chỉ cần không tăng thêmnpm run buildcho site frontendnpx wrangler deploy --dry-runcho APIwrangler dev --remoterồi gọi thử endpoint bị ảnh hưởng- Dọn sạch dữ liệu test đã tạo trong lúc kiểm thử
Tên miền tuỳ chỉnh
Thêm domain cho một Pages project phải làm trong bảng điều khiển Cloudflare: Pages → project → Custom domains. Không thao tác được từ repo.
docs.conan.school đã gắn domain từ 25.08.2026 (CNAME docs → docs-conan-school.pages.dev).
content.conan.school (Vite + Tailwind + shadcn + Motion, trang hello world, 06.10.2026) là ngoại lệ: deploy-content.yml có job domain tự gắn tên miền vào Pages project content-conan-school qua API. Bản ghi DNS CNAME content → content-conan-school.pages.dev (proxied) đã tạo tay một lần ở Cloudflare, vì token CI không có Zone DNS Edit nên workflow không tạo được nó.
Kiểm D1 sau migration
deploy-api chạy api.conan.school/scripts/verify-d1-after-migrate.mjs giữa bước áp migration và bước deploy mã.
Lý do: migration áp xong chỉ chứng minh câu lệnh chạy được, không chứng minh dữ liệu còn đúng.
Vị trí giữa hai bước là cố ý. Hỏng ở đây thì D1 đi trước mã, chiều lệch an toàn: bảng mới nằm đó mà mã cũ không đọc thì không ai thấy gì. Chiều ngược lại, mã mới đọc bảng chưa có, hỏng ngay trước mặt người dùng, và đã xảy ra thật với /api/events ngày 03.09.2026.
Phép kiểm chia hai loại: bất biến cứng phải bằng 0 (tham chiếu treo, quiz trỏ đáp án ra ngoài mảng phương án, sổ migration lệch số file) và mốc chặn chỉ cấm tăng (nợ có sẵn). Không dùng con số chụp lại làm ngưỡng, thêm nội dung là đỏ, và một cái gác kêu oan là một cái gác bị tắt.
Giảm phút CI (28.09.2026)
Đo 100 lượt chạy gần nhất: deploy-api chiếm ~70% phút, nhưng phần lớn từ vài lượt áp hàng trăm migration nội dung; lượt thường ~1 phút. Đòn bẩy thật:
- GitHub tính phút theo TỪNG JOB, làm tròn lên. Job
guardsđã gộp vào jobchangescủaci-checks.yml(cùng checkout full-history), bớt 1 phút tính tiền mỗi lượt PR. - Deploy chỉ checkout thư mục cần (
sparse-checkout: app_dir +packages+scripts, cone giữ file gốc) và cache npm theo lockfile của app. Repo ~350 MB, phần lớn là SVG và migration nội dung mà một site không cần. - deploy-api không chạy khi chỉ đổi nguồn nội dung (
content/*trừcontent/bmc, thư mục worker import thẳng, script.py,.md). Migration vẫn kích hoạt deploy như cũ. - Site tĩnh (com, www, docs)
cancel-in-progress: true: lượt mới thay lượt đang chờ. Api giữfalse. - Gộp thay đổi thành ít PR hơn. Mỗi PR nhỏ tốn ~3 phút CI + 1 phút mỗi site deploy dù chỉ đổi một dòng, gom nhiều sửa đổi liên quan vào một PR là cách rẻ nhất.