Sẽ mở trong thẻ mới
PDL AIAI Worker — trợ lý AI trực 24/7: trả lời khách, xử lý yêu cầu, báo cáo tự động

Kỹ sư phần mềm thời AITờ 01-08 / 112

Môi trường local, staging, production và biến môi trường

Ba phòng bếp thử, nếm thử và tiệm bán, mỗi phòng một kho riêng, ví cho local, staging và production
Tờ
01-08 / 112
Hạng mục
M01 · Nền tảng máy tính và web cho người không chuyên
Cấp độ
Cơ bản
Thời gian đọc
20 phút
Giai đoạn công trình
0 · Khám phá
Cập nhật
Bản đồ công trìnhTờ này lắp: Hosting & tên miền, Database
Bản đồ công trình Mộc OMSBài này: Hosting & tên miền, Database. Đã học: Coding agent, Khách hàng, Kênh bán, Sheet & sổ tay. Chưa tới: PRD & spec, Repo & CI, Kiểm thử, Giám sát lỗi, Giao diện app, API, Dashboard, báo cáo, Trợ lý AI nhận đơn, Màn hình bếp, Đăng nhập, vai trò, Thanh toán VietQR, Thông báo khách, Nhiều tiệm (MộcOS), Kế toán, hoá đơn.Thi công & hạ tầngDòng đơn hàngNghiệp vụ & dịch vụMở rộng MộcOSPRD & specspecCoding agentagentRepo & CIrepo-ciKiểm thửtestsHosting & tên miềnhostingGiám sát lỗimonitoringKhách hàngcustomerKênh bánchannelsGiao diện appapp-uiAPIapiDatabasedatabaseDashboard, báo cáoreportsSheet & sổ taysheetTrợ lý AI nhận đơnai-assistantMàn hình bếpkitchenĐăng nhập, vai tròauthThanh toán VietQRpaymentsThông báo kháchnotificationsNhiều tiệm (MộcOS)tenantsKế toán, hoá đơnintegrations0 Khám phá1 MVP2 Ra mắt3 Thêm AI4 Mở rộng
Bản đồ công trình Mộc OMSBài này: Hosting & tên miền, Database. Đã học: Coding agent, Khách hàng, Kênh bán, Sheet & sổ tay. Chưa tới: PRD & spec, Repo & CI, Kiểm thử, Giám sát lỗi, Giao diện app, API, Dashboard, báo cáo, Trợ lý AI nhận đơn, Màn hình bếp, Đăng nhập, vai trò, Thanh toán VietQR, Thông báo khách, Nhiều tiệm (MộcOS), Kế toán, hoá đơn.Dòng đơn hàngKhách hàngKênh bánGiao diệnAPIDatabaseBáo cáoNghiệp vụ & dịch vụSheet, sổ tayTrợ lý AIMàn hình bếpĐăng nhậpThanh toánThông báoThi công & hạ tầngPRD & specCoding agentRepo & CIKiểm thửHostingGiám sátMở rộng MộcOSNhiều tiệmKế toán0 Khám phá1234

Bài nàyĐã họcChưa tớiĐã thay thế

Bạn sẽ học được gì

Tiêu chí nghiệm thu

Ba môi trường: bếp thử, buổi nếm thử, tiệm đang bán

Khi anh Tuấn nghĩ ra công thức bánh mousse chanh dây mới, anh không mang mẻ đầu tiên ra tủ kính. Quy trình thường là:

  1. Làm thử trong bếp vài lần, sai thì đổ đi, không ai biết.
  2. Mời chị Lan và vài nhân viên nếm thử, trong điều kiện giống hệt khi bán: cùng hộp đựng, cùng nhiệt độ tủ, cùng thời gian từ lúc làm tới lúc ăn.
  3. Đưa vào menu khi mọi người đồng ý.

Phần mềm đi đúng ba bước như vậy, mỗi bước là một môi trường (environment): một bản cài đặt riêng của ứng dụng, với dữ liệu và cấu hình riêng.

Môi trường Ẩn dụ Ai dùng Dữ liệu Địa chỉ ví dụ Hỏng thì sao
Local (development) Bếp thử ở nhà Bạn, agent, lập trình viên Dữ liệu mẫu, giả http://localhost:3000 Không ai biết; sửa rồi chạy lại
Staging Buổi nếm thử nội bộ Chị Lan, Hà, anh Tuấn kiểm tra trước khi phát hành Dữ liệu thử nghiệm, giống thật nhưng không phải thật https://staging.oms.banhmoc.vn Phát hiện lỗi trước khách; chậm phát hành một chút
Production Tiệm đang bán Nhân viên 3 chi nhánh, khách hàng Dữ liệu thật: đơn, khách, tiền https://oms.banhmoc.vn Đơn sai, khách giận, mất tiền, mất uy tín

Ngoài ba môi trường chính, các nền tảng hosting hiện đại còn tạo preview (bản xem trước) tự động cho từng thay đổi: mỗi khi agent đề xuất một thay đổi qua pull request, có một địa chỉ riêng để bạn bấm thử trước khi gộp vào. Preview, staging và cách bật tính năng mới cho một nhóm nhỏ được hướng dẫn kỹ ở bài Môi trường preview, staging và feature flag10-06.

Đường đi của một thay đổi

Hình chữ ký Sơ đồ hệ thống
  1. LOCAL (máy chị Lan)
  2. STAGING / PREVIEW
  3. PRODUCTION
  4. DB local
  5. DB staging
  6. DB production
  7. đẩy code lên (git push)
  8. duyệt xong, phát hành
Xem sơ đồ gốc (Hình 1)

Vuốt ngang để xem hết sơ đồ

PRODUCTION

STAGING / PREVIEW

LOCAL (máy chị Lan)

đẩy code lên (git push)

duyệt xong, phát hành

Agent viết code
chạy test

DB local
dữ liệu mẫu

Bản thử nghiệm
Hà và anh Tuấn bấm thử

DB staging
dữ liệu thử

oms.banhmoc.vn
3 chi nhánh dùng thật

DB production
dữ liệu thật

Vuốt ngang để xem hết sơ đồ

PRODUCTION

STAGING / PREVIEW

LOCAL (máy chị Lan)

đẩy code lên (git push)

duyệt xong, phát hành

Agent viết code
chạy test

DB local
dữ liệu mẫu

Bản thử nghiệm
Hà và anh Tuấn bấm thử

DB staging
dữ liệu thử

oms.banhmoc.vn
3 chi nhánh dùng thật

DB production
dữ liệu thật

Hình 1 Sơ đồ luồng
Đọc sơ đồ dạng chữ
  1. LOCAL (máy chị Lan)
  2. Agent viết code chạy test → DB local dữ liệu mẫu
  3. STAGING / PREVIEW
  4. Bản thử nghiệm Hà và anh Tuấn bấm thử → DB staging dữ liệu thử
  5. PRODUCTION
  6. oms.banhmoc.vn 3 chi nhánh dùng thật → DB production dữ liệu thật
  7. Agent viết code chạy test → Bản thử nghiệm Hà và anh Tuấn bấm thử (đẩy code lên (git push))
  8. Bản thử nghiệm Hà và anh Tuấn bấm thử → oms.banhmoc.vn 3 chi nhánh dùng thật (duyệt xong, phát hành)

Hãy để ý hai điều trong sơ đồ:

  • Code đi một chiều: local → staging → production. Không ai sửa code trực tiếp trên production.
  • Mỗi môi trường có database riêng. Không có mũi tên nào nối local tới database production. Đó là điều cố ý, và là điều quan trọng nhất trong bài.

Vì sao không dùng chung một database cho tiện?

Hãy tưởng tượng chị Lan đang nhờ agent viết tính năng “xóa đơn thử nghiệm”, và agent chạy một đoạn script dọn dẹp. Nếu máy local đang trỏ vào database production, đoạn script đó sẽ dọn dẹp đơn thật của khách. Agent không có ác ý; nó chỉ làm đúng điều được viết, trên đúng database được cấu hình.

Tách database theo môi trường là hàng rào vật lý: dù agent hay bạn có nhầm lẫn thế nào ở local, dữ liệu khách hàng vẫn an toàn vì local không hề có chìa khóa vào kho thật.

Biến môi trường: cùng công thức, khác nguyên liệu

Nếu ba môi trường dùng ba database khác nhau, làm sao cùng một bộ code biết phải kết nối tới database nào?

Ẩn dụ: Bánh Mộc có ba chi nhánh dùng cùng một cuốn công thức. Nhưng mật khẩu Wi-Fi, số điện thoại nhà cung cấp sữa, mã kho của mỗi chi nhánh khác nhau. Không ai in những thông tin đó vào cuốn công thức; chúng được ghi trên tấm bảng dán ở cửa từng chi nhánh. Công thức chỉ ghi “gọi nhà cung cấp sữa (xem bảng ở cửa)”.

Biến môi trường (environment variable) chính là tấm bảng ở cửa. Đó là các cặp TÊN=giá trị được đặt bên ngoài code, trong môi trường nơi ứng dụng chạy. Code chỉ đọc theo tên:

TS
// Code chỉ biết TÊN biến, không biết giá trị. Mỗi môi trường cung cấp giá trị riêng.
const databaseUrl = process.env.DATABASE_URL;
  • Trên máy chị Lan, DATABASE_URL trỏ tới database local.
  • Trên staging, nó trỏ tới database staging.
  • Trên production, nó trỏ tới database thật.

Code giống hệt nhau ở ba nơi. Chỉ có tấm bảng ở cửa là khác.

Những gì nên là biến môi trường

Loại Ví dụ trong Mộc OMS Bí mật?
Địa chỉ và mật khẩu kết nối database DATABASE_URL ✅ Bí mật
Khóa API dịch vụ bên ngoài RESEND_API_KEY (email), khóa mô hình AI ✅ Bí mật
Khóa quản trị của Supabase (bỏ qua RLS) SUPABASE_SERVICE_ROLE_KEY ✅ Rất bí mật
Bí mật xác minh webhook PAYMENT_WEBHOOK_SECRET ✅ Bí mật
Địa chỉ và khóa công khai của Supabase NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY ❌ Công khai (an toàn nhờ RLS)
Tên môi trường APP_ENV=staging ❌
Bật/tắt tính năng theo môi trường ENABLE_ZALO_NOTIFY=false ở local ❌

Những gì không nên là biến môi trường: quy tắc nghiệp vụ (phí ship, ngưỡng miễn phí ship) thay đổi theo quyết định kinh doanh, nên nằm trong database hoặc code có test; danh sách sản phẩm; nội dung giao diện.

Nguyên tắc 12-factor: tách cấu hình khỏi code

The Twelve-Factor App (12factor.net) là bộ 12 nguyên tắc nổi tiếng để xây ứng dụng web dễ triển khai và vận hành. Bạn chưa cần đọc cả 12; ở mức nhập môn, hãy nắm nguyên tắc số III, Config (cấu hình):

  • Cấu hình là mọi thứ thay đổi giữa các môi trường triển khai: địa chỉ database, khóa dịch vụ bên ngoài, tên miền.
  • Cấu hình phải được tách hẳn khỏi code và lưu trong biến môi trường.
  • Phép thử đơn giản mà 12-factor đưa ra: nếu ngay lúc này bạn công khai toàn bộ mã nguồn lên mạng, có lộ khóa bí mật nào không? Nếu có, cấu hình chưa được tách đúng.

Phép thử này rất đáng dùng với chủ doanh nghiệp. Bạn có thể hỏi agent, hỏi freelancer, hỏi agency: “Nếu tôi công khai repo này ngay bây giờ, có lộ gì không?” Câu trả lời ngập ngừng là dấu hiệu cần kiểm tra.

Một nguyên tắc khác của 12-factor cũng liên quan trực tiếp: giữ các môi trường càng giống nhau càng tốt (dev/prod parity). Staging nên dùng cùng loại database, cùng phiên bản, cùng cách triển khai với production. Staging “gần giống” production thì lỗi chỉ xuất hiện trên production vẫn lọt qua.

File .env và .env.example

Gõ biến môi trường bằng tay mỗi lần chạy ứng dụng thì quá phiền. Trên máy local, người ta ghi chúng vào một file tên là .env (hoặc biến thể như .env.local). Ứng dụng tự đọc file này khi khởi động. Next.js hỗ trợ sẵn việc này.

Cú pháp file .env

BASH
# Dòng bắt đầu bằng # là chú thích
APP_ENV=local
DATABASE_URL=postgresql://postgres:mat-khau-local@localhost:54322/postgres
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOi...khoa-cong-khai-local
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOi...khoa-bi-mat-local
ENABLE_ZALO_NOTIFY=false

Quy tắc: mỗi dòng một biến, dạng TÊN=giá_trị, không có dấu cách quanh dấu =, tên viết HOA với dấu gạch dưới. Giá trị có dấu cách thì đặt trong ngoặc kép.

Các file .env trong Next.js

Next.js đọc một số file theo quy ước (tại thời điểm viết, 2026; xem tài liệu chính thức để biết chi tiết thứ tự ưu tiên):

File Dùng khi Đưa vào Git?
.env Giá trị mặc định cho mọi môi trường Chỉ khi không chứa bí mật; nhiều đội chọn không dùng
.env.local Giá trị thật trên máy bạn, ghi đè các file khác ❌ Không bao giờ
.env.development, .env.production Giá trị mặc định riêng cho chế độ chạy dev / build Chỉ khi không chứa bí mật
.env.example Mẫu: danh sách tên biến, không có giá trị thật ✅ Luôn luôn

Trên staging và production, bạn không dùng file .env. Bạn nhập biến môi trường vào trang quản trị của nền tảng hosting (xem phần sau).

.env.example: danh sách tên trên tấm bảng, để trống giá trị

Nếu .env.local không vào Git, làm sao một người mới (hoặc agent ở một phiên làm việc mới) biết dự án cần những biến nào? Câu trả lời là .env.example: một bản sao chỉ có tên biến và chú thích, giá trị để trống hoặc là giá trị giả rõ ràng.

BASH.env.example
# .env.example — MẪU biến môi trường cho Mộc OMS. File này ĐƯỢC đưa vào Git.
# Cách dùng: sao chép thành .env.local rồi điền giá trị thật. KHÔNG điền giá trị thật vào file này.

# Môi trường: local | staging | production
APP_ENV=local

# Kết nối database PostgreSQL (Supabase). BÍ MẬT, chỉ dùng ở server.
DATABASE_URL=

# Supabase: địa chỉ và khóa công khai. Được phép xuất hiện trên trình duyệt (an toàn nhờ RLS).
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=

# Supabase service role: BÍ MẬT, bỏ qua RLS. Chỉ dùng ở server. Tuyệt đối không thêm tiền tố NEXT_PUBLIC_.
SUPABASE_SERVICE_ROLE_KEY=

# Gửi email giao dịch (giai đoạn 2). BÍ MẬT.
RESEND_API_KEY=

# Bí mật xác minh webhook thanh toán (giai đoạn 2). BÍ MẬT.
PAYMENT_WEBHOOK_SECRET=

# Bật gửi thông báo Zalo. Để false ở local và staging để không nhắn nhầm cho khách thật.
ENABLE_ZALO_NOTIFY=false

Và file .gitignore phải có những dòng chặn file bí mật (nhiều bộ khởi tạo dự án Next.js đã thêm sẵn, nhưng hãy tự kiểm tra):

BASH
# .gitignore (trích)
.env*.local
.env
!.env.example

Dòng !.env.example nghĩa là “ngoại trừ file này”, để chắc chắn file mẫu vẫn được đưa vào Git.

Kiểm tra biến môi trường ngay khi khởi động

Một lỗi khó chịu: quên khai báo một biến trên staging, ứng dụng vẫn chạy, chỉ hỏng khi ai đó bấm đúng nút gửi email. Cách phòng: kiểm tra mọi biến cần thiết ngay lúc ứng dụng khởi động, thiếu là dừng và báo rõ. Agent có thể viết đoạn này bằng Zod (thư viện kiểm tra dữ liệu bạn đã gặp):

TSsrc/lib/env.ts
// src/lib/env.ts — chỉ dùng ở phía server.
// Kiểm tra biến môi trường khi khởi động; thiếu hoặc sai thì dừng ngay với thông báo rõ ràng.
import { z } from "zod";

const EnvSchema = z.object({
  APP_ENV: z.enum(["local", "staging", "production"]),
  DATABASE_URL: z.string().min(1, "Thiếu DATABASE_URL"),
  NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
  NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
  SUPABASE_SERVICE_ROLE_KEY: z.string().min(1, "Thiếu SUPABASE_SERVICE_ROLE_KEY"),
  ENABLE_ZALO_NOTIFY: z.enum(["true", "false"]).default("false"),
});

export const env = EnvSchema.parse(process.env);

Khi thiếu biến, ứng dụng dừng với thông báo chỉ đích danh biến nào. Lỗi lộ ra ngay khi triển khai, không phải giữa giờ cao điểm ngày 20/10.

Bàn thử

Dán .env.example (chỉ tên biến, chạy hoàn toàn trên trình duyệt) → cảnh báo biến bí mật mang tiền tố NEXT_PUBLIC_ (KEY, SECRET, TOKEN, SERVICE_ROLE), dòng có giá trị, .gitignore thiếu ‘!.env.example’; kèm bài phân loại 7 mục cấu hình

Bàn thử cần bật JavaScript.

Kết quả chỉ để tham khảo, không thay cho việc bạn tự duyệt.

Biến môi trường trên hosting

Trên staging và production, biến môi trường được nhập vào trang quản trị của nền tảng hosting. Với Vercel (trong stack Bánh Mộc), tại thời điểm viết, bạn vào phần cài đặt dự án, mục biến môi trường, và chọn mỗi biến áp dụng cho môi trường nào: Production, Preview, hay Development. Các nền tảng khác có cơ chế tương tự. Giao diện có thể thay đổi; hãy xem tài liệu chính thức của nền tảng bạn dùng.

Ba điều cần biết:

  • Đổi biến môi trường thường cần triển khai lại thì ứng dụng mới nhận giá trị mới. “Đã đổi khóa mà vẫn lỗi” thường là do quên bước này.
  • Mỗi môi trường một bộ khóa riêng: staging dùng project Supabase staging với khóa staging; production dùng project và khóa riêng. Lộ khóa staging không ảnh hưởng dữ liệu thật.
  • Giới hạn người được xem khóa production: chỉ chị Lan (owner) và người thật sự cần. Bài Quản lý secrets09-03 đi sâu vào phân quyền và xoay vòng khóa.

Câu chuyện chị Lan lộ API key

Đây là câu chuyện chị Lan vẫn kể lại mỗi khi có ai hỏi “sao phải học bài bản thế”.

Hồi còn thử “vibe” app quản lý đơn bằng một công cụ tạo app tự động, chị muốn thêm tính năng “AI tự tóm tắt ghi chú đơn hàng”. Công cụ gợi ý dán khóa API của một dịch vụ mô hình AI vào. Chị tạo khóa, dán vào ô được chỉ. Mọi thứ chạy ngay, chị rất vui.

Chị không biết ba điều:

  1. Khóa được đặt trong một biến có tiền tố NEXT_PUBLIC_, nghĩa là nằm trong code gửi xuống trình duyệt của bất kỳ ai mở app.
  2. Chị đã bấm “đẩy code lên GitHub” để “lưu cho an toàn”, với repo để chế độ công khai (mặc định chị không để ý). Trong một file cấu hình, khóa còn được ghi thẳng vào code.
  3. Trên Internet có những chương trình tự động liên tục quét các repo công khai để tìm khóa bị lộ. Khóa của chị có thể bị phát hiện trong thời gian rất ngắn sau khi đẩy lên.

Khoảng một tuần sau, chị nhận email từ nhà cung cấp dịch vụ AI cảnh báo mức sử dụng bất thường. Hóa đơn tháng đó cao gấp nhiều lần mức chị dự tính cho cả năm. Ai đó đã dùng khóa của chị để chạy việc của họ, và chị trả tiền.

Phản xạ đầu tiên của chị là xóa file chứa khóa và đẩy lên lại. Không đủ. Một người bạn làm kỹ sư giải thích:

  • Git lưu toàn bộ lịch sử; khóa vẫn nằm trong các phiên bản cũ của repo. Ai đã tải repo về thì vẫn có.
  • Khóa còn nằm trong bản app đang chạy trên mạng, vì nó được nhúng vào code trình duyệt.
  • Khóa đã lộ phải được coi là đã bị đánh cắp. Cách duy nhất là vô hiệu hóa nó.

Quy trình đúng mà chị làm theo, và giờ là quy trình chuẩn của Bánh Mộc khi nghi lộ khóa:

Vuốt ngang để xem hết sơ đồ

Phát hiện hoặc nghi ngờ lộ khóa

1. Thu hồi / vô hiệu hóa khóa NGAY
trên trang quản trị nhà cung cấp

2. Tạo khóa mới, đặt vào biến môi trường
trên hosting, triển khai lại

3. Kiểm tra thiệt hại: log sử dụng,
hóa đơn, dữ liệu bị truy cập

4. Liên hệ nhà cung cấp nếu có
chi phí bất thường

5. Sửa tận gốc: chuyển khóa về backend,
bỏ khỏi code, kiểm tra .gitignore

6. Đặt giới hạn chi tiêu, cảnh báo
sử dụng bất thường nếu dịch vụ hỗ trợ

Hình 2 Sơ đồ luồng
Đọc sơ đồ dạng chữ
  1. Phát hiện hoặc nghi ngờ lộ khóa → 1. Thu hồi / vô hiệu hóa khóa NGAY trên trang quản trị nhà cung cấp
  2. 1. Thu hồi / vô hiệu hóa khóa NGAY trên trang quản trị nhà cung cấp → 2. Tạo khóa mới, đặt vào biến môi trường trên hosting, triển khai lại
  3. 2. Tạo khóa mới, đặt vào biến môi trường trên hosting, triển khai lại → 3. Kiểm tra thiệt hại: log sử dụng, hóa đơn, dữ liệu bị truy cập
  4. 3. Kiểm tra thiệt hại: log sử dụng, hóa đơn, dữ liệu bị truy cập → 4. Liên hệ nhà cung cấp nếu có chi phí bất thường
  5. 4. Liên hệ nhà cung cấp nếu có chi phí bất thường → 5. Sửa tận gốc: chuyển khóa về backend, bỏ khỏi code, kiểm tra .gitignore
  6. 5. Sửa tận gốc: chuyển khóa về backend, bỏ khỏi code, kiểm tra .gitignore → 6. Đặt giới hạn chi tiêu, cảnh báo sử dụng bất thường nếu dịch vụ hỗ trợ

Ba bài học chị Lan rút ra, và cũng là ba quy tắc trong file AGENTS.md của dự án Mộc OMS:

  1. Khóa bí mật chỉ sống trong biến môi trường ở backend; không bao giờ trong code, không bao giờ có tiền tố NEXT_PUBLIC_.
  2. Repo mặc định để riêng tư; .gitignore chặn mọi file .env trừ .env.example.
  3. Mọi dịch vụ tính tiền theo lượt dùng đều đặt giới hạn chi tiêu và cảnh báo, nếu nhà cung cấp hỗ trợ.

Biến môi trường và coding agent

Làm việc với agent đặt thêm vài lưu ý riêng:

  • Agent có thể đọc file trong dự án, kể cả .env.local. Nội dung đó đi vào ngữ cảnh của agent. Nhiều công cụ cho phép cấu hình chặn agent đọc file nhất định; hãy dùng tính năng này cho các file .env* (trừ .env.example), và xem hướng dẫn ở bài Bảo mật khi dùng agent09-07.
  • Agent đôi khi viết thẳng khóa vào code “để chạy thử cho nhanh”, hoặc điền giá trị thật vào .env.example. Khi review thay đổi, hãy tìm các chuỗi dài ngẫu nhiên hoặc các chữ như key, secret, token, password.
  • Agent có thể in biến môi trường ra log khi gỡ lỗi (ví dụ in toàn bộ process.env). Log có thể bị lưu lại ở nơi nhiều người xem được.
  • Khi agent cần một biến mới, yêu cầu nó: thêm tên vào .env.example kèm chú thích, thêm vào đoạn kiểm tra khởi động, và báo bạn để bạn tự điền giá trị thật.

Áp dụng vào dự án Bánh Mộc

Nhật ký công trình · Bánh Mộc

BM-261012-014

12.10.2026

  • Chị LanChủ chuỗi
Hà ngập ngừng trước màn hình có dải cam cảnh báo môi trường thử, chị Lan chỉ vào dải cam

Trước khi viết tính năng đầu tiên cho Mộc OMS, chị Lan dành một buổi để “dựng hàng rào” cho ba môi trường:

  1. Hai project Supabase riêng: banhmoc-staging và banhmoc-production. Trên máy, chị dùng Supabase chạy local với dữ liệu mẫu.
  2. File .env.example như mẫu ở trên, đưa vào Git. .env.local trên máy chị chỉ chứa khóa local.
  3. Biến môi trường trên Vercel: bộ khóa staging cho Preview, bộ khóa production cho Production. Chỉ chị Lan có quyền xem khóa production.
  4. Kiểm tra biến môi trường khi khởi động bằng src/lib/env.ts.
  5. Dải băng cảnh báo trên staging: chị yêu cầu agent hiển thị một dải màu cam “MÔI TRƯỜNG THỬ NGHIỆM, KHÔNG NHẬP ĐƠN THẬT” khi APP_ENV khác production. Lý do rất thực tế: Hà từng suýt nhập đơn thật của khách vào bản thử vì hai giao diện giống hệt nhau.
  6. Tắt thông báo Zalo ở local và staging (ENABLE_ZALO_NOTIFY=false), để khi thử nghiệm không có khách hàng thật nào nhận tin “Đơn của bạn đã bị hủy”.

Cuối buổi, chị làm phép thử 12-factor: “Nếu công khai repo này ngay bây giờ, có lộ gì không?” Chị nhờ agent quét toàn bộ repo và lịch sử commit tìm chuỗi giống khóa bí mật. Kết quả sạch. Lần này chị biết vì sao nó sạch.

Prompt mẫu

Prompt 1: thiết lập cấu hình môi trường cho dự án.

Thiết lập quản lý biến môi trường cho dự án Next.js này theo nguyên tắc 12-factor:
1. Liệt kê mọi giá trị cấu hình hiện đang viết thẳng trong code (URL, khóa, mật khẩu, tên miền).
2. Chuyển chúng thành biến môi trường; tạo .env.example chỉ có tên biến và chú thích tiếng Việt,
   ghi rõ biến nào bí mật, biến nào công khai.
3. Tạo src/lib/env.ts kiểm tra biến bằng Zod khi khởi động, thông báo lỗi chỉ rõ biến thiếu.
4. Kiểm tra .gitignore chặn .env và .env*.local nhưng giữ .env.example.
5. Kiểm tra không có biến bí mật nào mang tiền tố NEXT_PUBLIC_.
KHÔNG đọc hoặc in nội dung các file .env thật. Khi cần giá trị thật, hỏi tôi tên biến, tôi sẽ tự điền.

Prompt 2: phép thử “công khai repo”.

Giả sử tôi công khai repo này ngay bây giờ. Hãy kiểm tra xem có lộ bí mật nào không:
quét toàn bộ file đang theo dõi bởi Git và lịch sử commit, tìm chuỗi giống API key, token,
mật khẩu, chuỗi kết nối database, hoặc file .env bị commit. Báo cáo vị trí (file, commit),
KHÔNG in ra giá trị đầy đủ của bất kỳ bí mật nào (chỉ hiện vài ký tự đầu).
Nếu tìm thấy, đề xuất các bước xử lý theo thứ tự ưu tiên.

Prompt 3: khi thêm một tích hợp mới cần khóa.

Tính năng gửi email xác nhận đơn cần khóa API của dịch vụ email. Hãy:
- Đặt tên biến RESEND_API_KEY, chỉ dùng ở server.
- Thêm vào .env.example kèm chú thích và vào schema trong src/lib/env.ts.
- Không viết giá trị thật ở bất kỳ đâu trong code hay test; test dùng giá trị giả.
- Ở môi trường khác production, chỉ ghi log "đã giả lập gửi email" thay vì gửi thật.
Sau khi xong, nhắc tôi những biến cần thêm trên hosting cho từng môi trường.

Sai lầm thường gặp

  1. Sự cố 01

    Dùng chung database giữa local và production.

    Triệu chứng

    “Cho tiện, có dữ liệu thật để thử.” Một lệnh sai là mất đơn thật.

    Cách tránh

    Mỗi môi trường một database và một bộ khóa; máy local không bao giờ có khóa production.

  2. Sự cố 02

    Viết khóa thẳng vào code hoặc đặt tiền tố NEXT_PUBLIC_ cho khóa bí mật.

    Triệu chứng

    Khóa lộ qua Git hoặc qua code trình duyệt.

    Cách tránh

    Khóa chỉ nằm trong biến môi trường phía server; review mọi thay đổi tìm chữ key, secret, token.

  3. Sự cố 03

    Commit file .env.

    Triệu chứng

    Chỉ cần một lần là khóa nằm vĩnh viễn trong lịch sử Git.

    Cách tránh

    Kiểm tra .gitignore ngay khi tạo dự án; dùng .env.example để chia sẻ danh sách biến.

  4. Sự cố 04

    Xóa file chứa khóa và nghĩ là xong.

    Triệu chứng

    Khóa vẫn nằm trong lịch sử Git và trong các bản sao.

    Cách tránh

    Thu hồi khóa trước, tạo khóa mới, rồi mới dọn dẹp.

  5. Sự cố 05

    Staging khác xa production.

    Triệu chứng

    Staging dùng cấu hình khác, phiên bản khác, nên lỗi chỉ lộ trên production.

    Cách tránh

    Giữ staging giống production nhất có thể; chỉ khác ở dữ liệu và khóa.

  6. Sự cố 06

    Thử nghiệm gửi thông báo thật cho khách.

    Triệu chứng

    Staging nhắn Zalo cho số điện thoại khách thật.

    Cách tránh

    Biến bật/tắt tích hợp gửi ra ngoài, mặc định tắt ở local và staging; dữ liệu thử dùng số điện thoại giả.

Checklist

Phiếu nghiệm thu

Bài tập thực hành

  1. Bài 1

    Cơ bản

    Phân loại cấu hình

    Với mỗi mục sau, quyết định: biến môi trường bí mật, biến môi trường công khai, hay không nên là biến môi trường: (a) mật khẩu database; (b) ngưỡng miễn phí ship 500.000đ; (c) địa chỉ Supabase; (d) khóa gửi tin Zalo ZNS; (e) tên môi trường “staging”; (f) danh sách vị bánh kem; (g) bí mật xác minh webhook ngân hàng.

    Kết quả mong đợi

    Bí mật: (a), (d), (g). Công khai: (c), (e). Không nên là biến môi trường: (b) và (f), vì đó là dữ liệu hoặc quy tắc kinh doanh, nên nằm trong database hoặc code có test.

  2. Bài 2

    Cơ bản

    Viết .env.example cho dự án của bạn

    Liệt kê mọi dịch vụ bên ngoài dự án của bạn sẽ dùng (database, email, thanh toán, AI…). Viết file .env.example với tên biến và chú thích tiếng Việt cho từng cái, ghi rõ bí mật hay công khai.

    Kết quả mong đợi

    Một file mẫu mà người mới (hoặc agent ở phiên mới) đọc là biết cần chuẩn bị những khóa nào.

  3. Bài 3

    Nâng cao

    Diễn tập sự cố lộ khóa

    Tạo một khóa API thử nghiệm ở một dịch vụ bạn dùng (loại không tốn tiền hoặc có giới hạn chi tiêu thấp). Giả định nó bị lộ. Thực hiện đầy đủ quy trình sáu bước trong bài: thu hồi, tạo mới, cập nhật biến môi trường trên hosting, triển khai lại, kiểm tra log. Ghi lại thời gian từng bước.

    Kết quả mong đợi

    Bạn biết chính xác trang quản trị nào để thu hồi khóa, mất bao lâu để ứng dụng chạy lại với khóa mới. Khi sự cố thật xảy ra, bạn không phải tìm tòi trong lúc hoảng.

Thuật ngữ trong bài

Môi trường (environment)
Một bản cài đặt riêng của ứng dụng với dữ liệu và cấu hình riêng
Local / development
Môi trường trên máy người làm phần mềm
Staging
Môi trường thử nghiệm giống production, dùng để kiểm tra trước khi phát hành
Production
Môi trường thật, khách và nhân viên đang dùng
Preview
Bản xem trước tự động cho từng thay đổi
Cấu hình (config)
Những gì thay đổi giữa các môi trường: địa chỉ, khóa
Biến môi trường
Cặp TÊN=giá trị đặt ngoài code, code đọc theo tên
.env / .env.local
File chứa biến môi trường trên máy local; không vào Git
.env.example
File mẫu chỉ có tên biến; luôn vào Git
.gitignore
Danh sách file Git bỏ qua
NEXT_PUBLIC_
Tiền tố khiến biến được nhúng vào code trình duyệt (công khai)
12-factor
Bộ 12 nguyên tắc xây ứng dụng dễ triển khai, vận hành
Dev/prod parity
Giữ các môi trường giống nhau nhất có thể
Thu hồi / xoay vòng khóa
Vô hiệu hóa khóa cũ, thay bằng khóa mới

Đọc thêm

Nghiệm thu tờ 01-08

Đọc xong và tự kiểm theo phiếu nghiệm thu, rồi đánh dấu tờ này.

Tác giả
Phú Digital
Đội ngũ PDL Digital — triển khai website, giải pháp AI và digital marketing cho doanh nghiệp.
Tư vấn 1:1

Muốn dựng phần mềm riêng cho doanh nghiệp của bạn?

Kỹ sư PDL cùng bạn chọn bước đầu tiên phù hợp — phản hồi trong giờ làm việc (T2–T7, 8h–18h).

<script src="https://cdn.jsdelivr.net/npm/page-flip/dist/js/page-flip.browser.js"></script>

<div id="container-flipbook" style="display:flex; justify-content:center;">
    <div id="my-flipbook">
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/AI-Google-Search.jpg" /> </div>
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/wearable-tech-advertising.jpg" /> </div>
       <div class="page"> <img src="https://pdl.vn/wp-content/uploads/2025/05/multimodal-embeddings-digital-marketing.jpg" /> </div>
    </div>
</div>
#my-flipbook {
    box-shadow: 0 0 20px rgba(0,0,0,0.2);
    margin: 20px auto;
    background-color: #f0f0f0;
}

.page {
    background-color: white;
    overflow: hidden;
}

.page img {
    width: 100%;
    height: 100%;
    object-fit: cover; /* Giúp ảnh vừa vặn trang sách */
}
// Đảm bảo thư viện đã sẵn sàng
function initFlipbook() {
    const el = document.getElementById("my-flipbook");
    if (!el || !window.St) return; // Nếu chưa thấy thư viện St thì thoát

    const pageFlip = new St.PageFlip(el, {
        width: 550,
        height: 733,
        size: "stretch",
        minWidth: 315,
        maxWidth: 1000,
        minHeight: 420,
        maxHeight: 1350,
        showCover: true,
        maxShadowOpacity: 0.5,
    });

    pageFlip.loadFromHTML(document.querySelectorAll(".page"));
}

// Chạy khởi tạo
if (document.readyState === 'complete') {
    initFlipbook();
} else {
    window.addEventListener('load', initFlipbook);
}
PDL · Tư vấn 1:1

Nhận tư vấn
miễn phí

Để lại thông tin, chuyên viên PDL sẽ liên hệ để hiểu nhu cầu và đề xuất giải pháp phù hợp ngân sách của bạn.

  • Phân tích nhanh hiện trạng website, quảng cáo, kênh bán hàng
  • Đề xuất lộ trình & báo giá minh bạch, rõ từng hạng mục
  • Không ràng buộc: nghe tư vấn xong mới quyết định
Giờ làm việc T2–T7 · 08:00–18:00

Chỉ mất khoảng 30 giây

Bạn cần tư vấn về

🔒 Thông tin được bảo mật, chỉ dùng để PDL liên hệ tư vấn.