Checklist lỗi .env thường gặp | Quote, khoảng trắng, thiếu key
Hầu hết lỗi .env không phải “dotenv hỏng” — mà là space quanh =, quote lệch, thiếu key so với .env.example, hoặc process đang đọc file khác (.env.local / Docker env_file). Sửa theo checklist dưới trước khi đụng code business. Giữ secret khỏi git; chỉ commit template.
Lead nhắn: “Clone về chạy không được — chắc thiếu dependency.” Bạn npm i lại lần nữa. Thật ra process.env.DATABASE_URL là undefined vì key trong .env.example chưa bao giờ được copy, hoặc value bị cắt vì dấu " thiếu.
Bài này là checklist lỗi .env thường gặp cho junior, freelance và team outsourcing Việt Nam — nơi onboard hay gửi screenshot .env qua chat và mỗi người paste một kiểu. Tập trung cú pháp + thiếu key + nhầm file, không lan man lý thuyết 12-factor.
Ai nên bookmark
- Lần đầu setup Node / Nest / Next / Vite có dotenv
- Nhận repo khách chỉ có
.env.exampletrống trải - Docker Compose “mất” biến dù local chạy ổn
- Review PR: phát hiện
.envsắp bị commit
Quy tắc vàng 30 giây
# Đúng
DATABASE_URL=postgres://user:pass@localhost:5432/app
APP_NAME="My App"
FEATURE_FLAG=true
# Sai / rủi ro cao
DATABASE_URL = postgres://... # space quanh =
APP_NAME=My App # space trong value không quote
APP_NAME="My App # thiếu đóng quote
SECRET=abc#comment # # có thể bị coi là comment
KEY=value— không space quanh=- Value có space /
#/ ký tự lạ → quote - Comment: ưu tiên dòng riêng bắt đầu bằng
# - Mỗi môi trường một file; không commit secret
- Onboard = copy
.env.example→.envrồi điền đủ key
Checklist lỗi thường gặp
| Triệu chứng | Nguyên nhân hay gặp | Cách sửa |
|---|---|---|
| undefined dù đã có dòng trong file | Sai tên key / đọc nhầm file / chưa restart | Diff với .env.example; restart dev server |
| Value bị cắt hoặc có dấu " thừa | Quote mở/đóng lệch; escape JSON sai | Khớp cặp nháy; thử bỏ quote nếu value đơn giản |
| Key lạ kiểu "API_KEY " | Space trước/sau tên hoặc quanh = | Viết KEY=value sát nhau |
| Chỉ fail trên Linux CI | CRLF từ Windows; BOM đầu file | LF; lưu UTF-8 không BOM |
| Local OK, container miss | Compose env_file / ${VAR} khác dotenv app | Kiểm tra env_file + environment trong compose |
| Lỗi URI / JSON parse | Thiếu quote URL; JSON một dòng chưa escape | Quote value; validate bằng formatter |
In checklist này vào ticket onboard — đỡ hơn giải thích lại mỗi sprint.
1) Khoảng trắng quanh = và trong value
Sai
API_KEY = sk_live_xxx
DB_HOST= localhost
Một số parser giữ space trong tên key (API_KEY ) hoặc value ( localhost). Code của bạn tìm API_KEY → miss.
Đúng
API_KEY=sk_live_xxx
DB_HOST=localhost
APP_TITLE="Staging Shop"
Rule: space trong value → bọc nháy. Space quanh = → xóa.
2) Quote: thiếu, thừa, hoặc lệch kiểu
| Tình huống | Gợi ý |
|---|---|
| Số / boolean / slug đơn | Không cần quote: PORT=3000, DEBUG=true |
| Có space | NAME="Cafe Ha Noi" |
Có # | Bắt buộc quote nếu không muốn bị comment |
| JSON ngắn | CONFIG="{\"a\":1}" — escape \ đúng mức thư viện |
| Nháy lệch | "abc' → parser ăn sang dòng sau |
Mẹo debug: log JSON.stringify(process.env.MY_KEY) (trên value giả) để thấy có còn dấu " bị nuốt hay \r ở cuối không. Không log secret production.
3) Thiếu key — lỗi im lặng nguy hiểm nhất
File .env “có đó” nhưng thiếu REDIS_URL trong khi code process.env.REDIS_URL! giả định luôn có. Runtime không báo “missing line 12” — bạn chỉ thấy timeout hoặc fallback sai.
Checklist thiếu key (làm mỗi lần clone)
- Mở
.env.example(hoặc docs Notion của team) - Liệt kê mọi
KEY= - So với
.envthật — đánh dấu thiếu / thừa / đổi tên - Biến bắt buộc (DB, auth secret) để trống cố ý → fail fast trong
config.tscòn hơn chạy nửa vời
Ví dụ fail-fast (Node):
function required(name: string): string {
const v = process.env[name];
if (!v) throw new Error(`Missing env: ${name}`);
return v;
}
export const dbUrl = required("DATABASE_URL");
Freelance nhận repo khách: 15 phút diff key tiết kiệm nửa ngày đoán bug “API 401” do thiếu JWT_SECRET.
4) Comment, dòng trống, và export thừa
# Good: comment riêng dòng
# Production DB — không commit
DATABASE_URL=postgres://...
# Risky: comment cuối dòng (một số lib cắt, một số giữ nguyên)
MODE=dev # local only
# Sai nếu shell-style lọt vào dotenv thuần
export API_KEY=xxx
Ưu tiên comment đầu dòng. Tránh export trừ khi file dành cho source trong bash. Dòng trống OK; dòng chỉ có space đôi khi tạo key rỗng kỳ lạ — xóa sạch.
5) Nhầm file / nhầm môi trường
| File | Ai đọc |
|---|---|
.env | dotenv mặc định nhiều app Node |
.env.local | Next.js / một số tool — ưu tiên cao, thường gitignore |
.env.development / .env.production | Framework load theo NODE_ENV |
docker-compose.yml → env_file | Container, không phải process trên host |
| Secret CI (GitHub Actions) | Không nằm trong file local |
Triệu chứng kinh điển: sửa .env mãi không ăn vì Next đang đọc .env.local cũ. Hoặc PM2/cluster không restart → env cũ còn trong memory.
6) Docker Compose: hai “vũ trụ” env
# .env cạnh compose: dùng cho ${IMAGE_TAG} khi parse file
services:
api:
env_file:
- .env.docker
environment:
NODE_ENV: production
DATABASE_URL: ${DATABASE_URL}
${DATABASE_URL}lấy từ môi trường host /.envmặc định của Composeenv_fileinject vào containerenvironment:có thể ghi đè
Local npm run dev đọc .env qua dotenv — không chứng minh container thấy cùng bộ key. Checklist: docker compose config (che secret khi paste) và printenv trong container (chỉ tên key).
7) CRLF, BOM, encoding tiếng Việt trong value
- File tạo trên Windows → CRLF (
\r\n). Một số parser để\rdính cuối value → so khớp chuỗi fail ("secret\r" !== "secret"). - Excel / Notepad “UTF-8 with BOM” → key đầu file thành
\uFEFFAPI_KEY. - Value tiếng Việt / path có dấu: dùng UTF-8, quote nếu cần; tránh copy từ Word (dấu
"cong).
Đồng bộ team: EditorConfig end_of_line = lf cho .env*.
Dùng formatter để lộ dòng lệch
Khi .env 80 dòng paste từ 3 người, mắt thường dễ miss. Dán vào .env Formatter trên Kawa: sort theo key, giữ comment, chạy local trên trình duyệt (không upload) — tiện soi key trùng, khoảng trống, thứ tự lộn xộn trước khi commit .env.example.
🛠️ Kiểm tra định dạng .env ngay tại đây
Workflow gợi ý:
- Paste
.env.example+ bản.envđã che secret - Format/sort → diff bằng mắt dễ hơn
- Copy lại template sạch vào repo
Không thay thế secret manager — chỉ giảm lỗi cú pháp và thiếu key.
Case study
A) Outsourcing: “DB sai” hóa ra space
DATABASE_URL= postgres://... (space sau =). Lib giữ space → URI invalid. Dev đổ lỗi Postgres. Sửa một ký tự xong green.
B) Freelance Next.js: sửa không ăn
Khách gửi .env; bạn sửa NEXT_PUBLIC_API_URL. Vẫn gọi staging cũ vì máy còn .env.local gitignore với URL cũ. Xóa/override .env.local mới đúng.
C) CI đỏ, laptop xanh
Key STRIPE_SECRET có trong .env local nhưng chưa khai báo GitHub Actions secrets. Thêm fail-fast required() khiến CI fail rõ “Missing env” thay vì lỗi Stripe khó đọc.
Bảo mật tối thiểu (vẫn trong checklist)
.gitignore:.env,.env.*.local(giữ.env.example)- Không paste secret vào issue/PR; dùng memo mã hóa ngắn hạn nếu buộc phải gửi tạm, rồi rotate
- Đã push nhầm → rotate, không chỉ xóa file
- Production: secret store (CI / vault / platform env), không dựa vào file trên disk shared
Checklist copy-paste trước khi hỏi team
- Mọi key trong
.env.exampleđều có trong.env - Không space quanh
= - Quote khớp; value có
#hoặc space đã được quote - Không
export, không comment cuối dòng nếu lib team không hỗ trợ - LF, không BOM
- Đúng file (
.envvs.env.localvs Composeenv_file) - Đã restart process / rebuild container
- Không commit secret;
.env.examplekhông chứa key thật
Tóm tắt
Lỗi .env hay gặp nhất là cú pháp nhỏ và thiếu key im lặng. Sửa checklist trước khi refactor code. Khi cần sắp xếp / soi key nhanh trên trình duyệt: .env Formatter.