---
url: https://docs.danghong.me/ecosystem/deployment.md
description: >-
  Cách build và deploy từng site danghong.me lên Cloudflare Pages: CI, GitHub
  Actions, wrangler, token, Access cho admin media, kiểm tra sau deploy.
---

# Deployment — CI and Cloudflare Pages

> **Hiện trạng 2026-10-23:** workflow `Deploy docs` và `Deploy media` đang thất bại trên `main`. Deploy tay theo mục 2.2 cho tới khi sửa ([Infrastructure](infrastructure.md) I5). Quy trình làm việc hiện tại: merge thẳng vào `main`, push, rồi deploy.

Ba site đều chạy trên Cloudflare Pages và được build từ cùng một repository. **Chỉ dùng Cloudflare**: Vercel đã bị tắt bằng `vercel.json` (`"git": { "deploymentEnabled": false }`) ở thư mục gốc và trong `website/`.

| Site | Thư mục | Pages project | Output | Cách deploy |
| :--- | :--- | :--- | :--- | :--- |
| danghong.me | `website/` | (đã có sẵn) | `website/out` | Cloudflare Pages kết nối Git (thiết lập trên Dashboard) |
| docs.danghong.me | `docs/` | `danghong-docs` | `docs/.vitepress/dist` | GitHub Actions + `wrangler pages deploy` |
| media.danghong.me | `media/` | `danghong-media` | `media/public` + `media/functions` (D1, R2) | GitHub Actions + `wrangler pages deploy` |

***

## 1. WEBSITE (danghong.me)

* **Build command**: `npm run build` (thư mục gốc của project: `website`).
* **Output**: `out/` (Next.js `output: "export"`).
* **Headers**: `website/public/_headers`.
* **Nội dung**: build fetch `https://media.danghong.me/api/website/*.json` (D1), nên cần mạng và media phải chạy. Xem [Data](data.md).
* **Environment Variables**: không bắt buộc. `CONTENT_API_URL` đổi nguồn nội dung (mặc định `https://media.danghong.me/api/website`). Quản lý trên Cloudflare Dashboard (không push file `.env`).
* **Build lại khi sửa nội dung** (thiết lập một lần): Pages project `danghong-me` → *Settings → Builds → Deploy hooks* → tạo hook cho nhánh `main`, rồi lưu URL làm secret của media:

  ```bash
  cd media
  npx wrangler pages secret put WEBSITE_DEPLOY_HOOK --project-name danghong-media
  ```

  Nút **Xuất bản website** ở https://media.danghong.me/admin/ gọi hook này. Chưa có secret thì nút bị tắt.

***

## 2. DOCS & MEDIA (WRANGLER QUA GITHUB ACTIONS)

Workflow `.github/workflows/deploy-docs.yml` và `deploy-media.yml` chạy khi có push vào `main` làm thay đổi `docs/**` hoặc `media/**` (hoặc chạy tay trong tab **Actions → Run workflow**). Mỗi workflow:

1. Cài dependency, rồi build (docs) hoặc chạy test (media; media không có bước build, Functions đọc D1/R2 lúc chạy).
2. Tạo Pages project nếu chưa có (`wrangler pages project create … --production-branch main`).
3. Deploy bằng `wrangler pages deploy … --branch main`.

Cấu hình project nằm trong `docs/wrangler.toml` và `media/wrangler.toml`. `media/wrangler.toml` khai báo binding D1 (`DB`) và R2 (`BUCKET`); Pages áp dụng chúng mỗi lần deploy. Nội dung media không deploy qua Git: sửa ở `/admin/` là lên ngay.

### 2.1. Thiết lập một lần

1. **Tạo API token** tại Cloudflare Dashboard → *My Profile → API Tokens → Create Token → Custom token*, quyền **Account › Cloudflare Pages › Edit**, **Account › D1 › Read** và **Account › Workers R2 Storage › Read** (để gắn binding của media), chọn account `Dangtuyethong2324@gmail.com's Account` (nơi giữ các Pages project).

2. **Lấy Account ID** ở trang *Workers & Pages* (cột bên phải).

3. **Thêm GitHub secrets** tại repo → *Settings → Secrets and variables → Actions*:
   * `CLOUDFLARE_API_TOKEN`
   * `CLOUDFLARE_ACCOUNT_ID`

4. **Chạy workflow lần đầu** (Actions → *Deploy docs* / *Deploy media* → Run workflow). Project `danghong-docs` và `danghong-media` sẽ được tạo.

5. **Gắn custom domain**: *Workers & Pages* → chọn project → *Custom domains* → *Set up a custom domain*:

   * `danghong-docs` → `docs.danghong.me`
   * `danghong-media` → `media.danghong.me`

   Vì `danghong.me` đã ở trên Cloudflare, bản ghi DNS (CNAME) được tạo tự động.

6. **Cloudflare Access cho trang quản trị media** (một lần, chỉ chủ site làm):

   1. Dashboard → *Zero Trust* (tạo team nếu chưa có; tên team là `<team>.cloudflareaccess.com`) → *Access → Applications → Add an application → Self-hosted*.
   2. Domain: `media.danghong.me`, path `admin`; thêm một application nữa (hoặc thêm destination) cho `danghong-media.pages.dev/admin` để bản preview cũng được chặn.
   3. Policy: *Allow*, Include → *Emails* → `dangtuyethong2324@gmail.com`. Login method: *One-time PIN* (hoặc Google).
   4. Copy *Application Audience (AUD) Tag* và tên team vào `ACCESS_AUD`, `ACCESS_TEAM_DOMAIN` trong `media/wrangler.toml`, rồi deploy media.

   Chưa có `ACCESS_TEAM_DOMAIN` thì `/admin/` trả 503.

### 2.2. Deploy từ máy cá nhân

Wrangler đăng nhập thấy hai account, nên cần chỉ rõ account:

```bash
npx wrangler login
export CLOUDFLARE_ACCOUNT_ID=574e12931f580feff7ebaf755990b8a2
cd docs  && npm run deploy
cd media && npm run deploy -- --branch main
```

***

## 3. KIỂM TRA SAU KHI DEPLOY

* `https://docs.danghong.me` mở được trang chủ tài liệu và ô tìm kiếm hoạt động.
* `https://media.danghong.me/api/index.json` trả về JSON, header có `Access-Control-Allow-Origin: *`.
* `https://media.danghong.me/photos/avatar.png` trả ảnh từ R2; `https://media.danghong.me/admin/` chuyển tới trang đăng nhập của Cloudflare Access.
* Tab *Deployments* của từng project hiển thị commit mới nhất của `main`.
