Skip to content
Bùi Thúc Đồng Notebook · since 2009
§ 03 · TUTORIAL INTERMEDIATE ~15 MIN UPDATED 2026-09-23

Đồng Use Claude — Phần 7: DEPLOY, đưa app lên Cloudflare miễn phí

Từ localhost ra internet: tạo D1 thật, wrangler deploy, chạy lại toàn bộ kịch bản test trên URL production — vì app chỉ tồn tại khi người khác mở được. Chi phí 0đ trên free tier.

App chạy trên localhost mới chỉ là bản nháp — nó “tồn tại” khi nhân viên mở được trên điện thoại ở tiệm. Bài này đưa sổ khách lên Cloudflare: Workers chạy code, D1 chứa data, cả hai nằm trên tài khoản của bạn và nằm gọn trong free tier.

Bài 7 trong series. Cần app đã pass nghiệm thu local trước — deploy một app chưa test là chuyển bug từ máy bạn lên internet.

Bước 1: Tài khoản và đăng nhập

Tạo tài khoản Cloudflare miễn phí (không cần thẻ), rồi trong thư mục dự án:

npx wrangler login

Lệnh mở trình duyệt để bạn bấm Allow — một lần duy nhất, wrangler tự lưu credential. Kiểm tra: npx wrangler whoami phải in ra email tài khoản bạn.

Bước 2: Tạo database D1 thật

Cho tới giờ DB chỉ là file local trong .wrangler/state. Tạo bản thật:

npx wrangler d1 create mini-crm-db

Output thật khi chạy cho app mẫu của series:

✅ Successfully created DB 'mini-crm-db' in region APAC
  "database_name": "mini-crm-db",
  "database_id": "7a9d431b-32c6-4b3a-9838-4d97c7c79206"

Mở wrangler.jsonc, thay placeholder bằng ID thật:

"d1_databases": [
  {
    "binding": "DB",
    "database_name": "mini-crm-db",
    "database_id": "dán-id-thật-vào-đây"
  }
]

Rồi apply schema lên DB thật — chú ý --remote thay vì --local:

npx wrangler d1 execute DB --remote --file=schema.sql

Bước 3: Deploy

npx wrangler deploy

Output thật:

Total Upload: 66.99 KiB / gzip: 17.19 KiB
Deployed mini-crm triggers (1.02 sec)
  https://mini-crm.bui-thuc-dong.workers.dev

Vài giây là app lên mạng tại https://mini-crm.<tên-bạn>.workers.dev — app mẫu của series đang chạy thật ở mini-crm.bui-thuc-dong.workers.dev (passkey demo dong-2026, lý do có khoá ở Bước 5). Mở lên, ai có link đều vào được — và đó chính là vấn đề sẽ giải quyết ở Bước 5.

Gotcha thật gặp khi chạy bài này: bắn ngay kịch bản test sau deploy ~30 giây, một request trả về 404 — route workers.dev chưa kịp propagate tới edge gần bạn. Không phải app lỗi; đợi thêm một phút rồi chạy lại là sạch. Ghi nhận này cũng là một kịch bản test tốt: môi trường production có những trạng thái trung gian mà local không có.

Bước 4: Nghiệm thu lại trên production — không bỏ bước này

Local pass không có nghĩa production pass: DB khác, môi trường khác. Chạy lại TEST-01 trên URL thật bằng cách đổi host:

APP=https://mini-crm.<tên-bạn>.workers.dev
PASSKEY=passkey-cua-ban   # app đã khoá — xem Bước 5

# TC-1: tạo khách
curl -s -X POST $APP/api/customers \
  -H 'content-type: application/json' -H "x-passkey: $PASSKEY" \
  -d '{"name":"An Nguyen","phone":"0901 234 567","note":"test trên production"}'

# TC-4: 10 request đồng thời — quan trọng nhất, vì D1
# thật chạy khác SQLite local
for i in $(seq 1 10); do
  curl -s -o /dev/null -w "%{http_code} " -X POST $APP/api/customers \
    -H 'content-type: application/json' -H "x-passkey: $PASSKEY" \
    -d "{\"name\":\"Race $i\",\"phone\":\"0988 111 222\"}" &
done; wait

# Kiểm trên DB thật
npx wrangler d1 execute DB --remote --command \
  "SELECT phone_norm, COUNT(*) AS n FROM customers GROUP BY phone_norm HAVING n > 1"
# kỳ vọng: results rỗng

Kết quả thật khi chạy lại trên production của series: 201 200×9, query HAVING n > 1 trả results: [] — không số phone nào trùng, kể cả trên D1 thật. Cập nhật kết quả vào TEST-01.md: “PASS trên production <url> ngày …”. Đây mới là ký nhận cuối.

Bước 5: Khoá app — giải quyết [TRD-3]

Nhớ trade-off trong spec: “ai có link đều xem được”. Trên production thật thì không chấp nhận nổi — đây là v2 đầu tiên, đi đúng quy trình rút gọn: ghi vào plan, build, test, deploy.

App mẫu của series đã có sẵn passkey gate: mọi route trừ /login đều bắt auth — trình duyệt qua form /login nhận cookie HttpOnly 7 ngày, script/API gửi header x-passkey. Không cấu hình passkey thì app đóng mặc định (fail closed).

Ba lệnh chạy thật:

# 1. Nạp passkey lên Worker (giữ trong secrets, không nằm trong code)
echo "passkey-cua-ban" | npx wrangler secret put APP_PASSKEY

# 2. Deploy lại
npx wrangler deploy

# 3. Nghiệm thu bước khoá — đây là TC-6 mới của TEST-02
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' $APP/
# → 302 .../login          (trang bị đá về login)
curl -s -o /dev/null -w '%{http_code}\n' $APP/api/customers
# → 401                    (API từ chối)
curl -s $APP/api/customers -H 'x-passkey: passkey-cua-ban'
# → {"customers":[...]}    (đúng passkey → vào)

Cả ba output trên là kết quả thật trên bản deploy của series. Demo sống ở mini-crm.bui-thuc-dong.workers.dev đã khoá — passkey demo là dong-2026, cố tình public để bạn vào nghịch; production thật đừng public passkey.

Cần chặn chắc hơn (chỉ email của nhân viên được vào, có audit log) thì dùng Cloudflare Access — free, cấu hình trong dashboard Zero Trust → Applications, không đụng code. Passkey gate ở đây là mức vừa đủ cho app nội bộ nhỏ.

Bước 6: Dọn dẹp dữ liệu test

Trước khi đưa link cho nhân viên dùng thật, xoá các row test:

npx wrangler d1 execute DB --remote --command \
  "DELETE FROM customers WHERE note LIKE '%Race%' OR note LIKE '%test%'"

Chi phí và quyền sở hữu

Free tier hiện tại dư sức cho quy mô này: Workers ~100.000 request/ngày, D1 vài GB storage và hàng triệu row-read/ngày — một sổ khách nội bộ không bao giờ chạm trần. Toàn bộ nằm trên tài khoản bạn tự tạo: code trong repo của bạn, data trong D1 của bạn, không ai giữ hộ.

Một việc nên làm tiếp khi app đi vào dùng thật: custom domain — nếu có domain riêng, Cloudflare dashboard → Workers → gắn domain, DNS tự lo. App bớt nhìn như đồ chơi.

Sau deploy: vòng lặp maintain

Từ giờ mỗi thay đổi đi hết vòng: sửa đổi nhỏ → viết INTENT/SPEC mới (INTENT-02, SPEC-02) → PLAN → BUILD → TEST → deploy lại. Không có ngoại lệ “sửa tí thôi không cần file” — sửa tí thôi mà không có file chính là cách dự án trôi lần thứ hai.

Bài tập

  1. Deploy app của bạn lên Workers theo đúng 6 bước trên — kể cả bước khoá.
  2. Chạy lại toàn bộ kịch bản test trên URL production (nhớ x-passkey) và cập nhật file TEST.
  3. Đưa link cho một người thật dùng thử 10 phút, quan sát họ bấm — ghi lại mọi chỗ họ lúng túng thành [PRB-n] cho INTENT-02.

Nguồn tham khảo

Bài tiếp theo: Phần 8 — WORKBOOK, bài cuối của series.