Cách Mình Quản Lý Environments
Cấu hình environment, quản lý secrets, và chiến lược deployment
Cùng một codebase chạy trên laptop, staging server, và production. Mỗi chỗ cần database khác, API keys khác, URLs khác. Quản lý mấy cái khác biệt này mà không rối -- đó là environment management.
"Cùng code, khác configuration."
Code ứng dụng không thay đổi giữa các environments. Chỉ configuration thay đổi: database connections, API keys, feature flags, URLs.
Các loại Environment
┌─────────────────────────────────────────────────────────────┐
│ DEVELOPMENT │
│ Local machine, hot reload, debug tools │
│ Database: Local / Docker │
│ API Keys: Test/sandbox credentials │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ STAGING │
│ Giống production, dùng để testing │
│ Database: Tách biệt khỏi production │
│ API Keys: Test/sandbox credentials │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PRODUCTION │
│ Real users, real data │
│ Database: Production │
│ API Keys: Live credentials │
└─────────────────────────────────────────────────────────────┘
Không bao giờ commit secrets
Secrets không thuộc về version control. Không có ngoại lệ.
"Commit API key tạm rồi đổi sau" -- ai cũng nói vậy, rồi quên. Key đó nằm trong git history mãi mãi, kể cả khi xóa file đi rồi.
Secrets đặt trong environment variables, set bên ngoài codebase. Ngay cả trên local machine cũng vậy.
Cái gì là secret:
- API keys và tokens
- Database passwords
- Encryption keys
- OAuth client secrets
- Webhook signing secrets
Commit được (không phải secret):
- Configuration templates với placeholder values
- Non-sensitive defaults
- Public API URLs
Document variables, đừng để người mới phải đoán
App startup fail với error "Cannot read property of undefined" mà không ai biết thiếu variable gì -- chuyện này xảy ra nhiều hơn mình tưởng.
Duy trì .env.example liệt kê mọi variable, kèm:
- Mô tả chức năng
- Example value (không phải real secret)
- Required hay optional
- Lấy valid value ở đâu
Onboarding developer mới mà mất nửa ngày hỏi "cần variables gì?" thì documentation đang có vấn đề rồi.
Validate sớm, fail nhanh
Typo trong tên variable mà mấy tuần không ai phát hiện. Rồi đến lúc 3 giờ sáng, critical feature fail vì DATABASE_URL bị viết sai thành DATABSE_URL. Chuyện thật.
Kiểm tra tất cả required configuration ngay khi app start. Thiếu gì thì crash ngay với error message rõ ràng -- chứ đừng để app chạy bình thường rồi fail ở runtime.
Validate cái gì:
- Required variables tồn tại
- Values đúng type (URLs là URLs, numbers là numbers)
- Connections hoạt động (connect được database không?)
Fail lúc startup thì fix trong 5 phút. Fail lúc 3 giờ sáng ở production thì fix trong 5 tiếng.
Tách biệt các loại configuration
Không phải configuration nào cũng giống nhau. API key thay đổi hiếm khi và cần bảo mật cao. Feature flag thay đổi thường xuyên và không nhạy cảm. Đối xử khác nhau thì mới quản lý được.
| Loại | Ví dụ | Thay đổi | Bảo mật |
|---|---|---|---|
| Secrets | API keys | Hiếm khi | Cao |
| Environment | Database URL | Theo environment | Trung bình |
| Feature flags | Enable UI mới | Thường xuyên | Thấp |
| Constants | Retry count | Hiếm khi | Thấp |
Secrets cần bảo vệ chặt. Feature flags cần thay đổi dễ dàng. Constants nằm trong code là được.
Development phải giống production
"Works on my machine" -- câu nói kinh điển trước khi bug xuất hiện ở production.
Local dùng SQLite, production dùng PostgreSQL? Sớm muộn gì cũng gặp incompatibility. Dùng cùng services locally mà production dùng. Docker làm việc này dễ thôi.
Mỗi khác biệt giữa environments là một bug tiềm ẩn chờ ngày nổ.
Khung quyết định
"Cái này có nên là environment variable không?"
Nên khi nó thay đổi giữa environments, là secret, muốn thay đổi mà không deploy lại, hoặc là connection string/URL.
Không cần khi nó không bao giờ thay đổi giữa environments, không nhạy cảm, và là constant định nghĩa behavior (retry counts, timeouts).
Test đơn giản: mình có muốn thay đổi cái này mà không cần deploy code mới không? Nếu có thì làm nó thành variable.
Đặt trong file nào?
.env.example (committed) -- template liệt kê tất cả variables, placeholder values, documentation.
.env.local (không committed) -- local overrides, API keys cá nhân, development-specific settings.
.env (đôi khi committed) -- non-sensitive defaults, shared development configuration.
Platform environment (cho production) -- tất cả secrets, production URLs, production-specific settings.
Tổ chức environment variables
Nhóm theo mục đích: database configuration, authentication, external APIs, feature flags, URLs.
Đặt tên nhất quán:
- Prefix related variables:
DATABASE_URL,DATABASE_POOL_SIZE - Dùng SCREAMING_SNAKE_CASE
- Mô tả rõ:
STRIPE_SECRET_KEYkhông phảiSK
Thêm comments trong template giải thích variables không rõ ràng, ghi chú nơi lấy values, đánh dấu required vs optional.
Khi thiếu variable thì sao?
Cho required variables: fail lúc startup, nêu tên variable bị thiếu, gợi ý cách sửa.
Cho optional variables: dùng sensible defaults, document default behavior, log khi falling back to defaults.
Không bao giờ: âm thầm dùng empty string, để app start rồi fail sau, hoặc nuốt error.
Database khác nhau cho mỗi environment
| Environment | Database | Set qua |
|---|---|---|
| Development | Local SQLite hoặc Docker PostgreSQL | .env.local hoặc default |
| Staging | Cloud PostgreSQL (tách biệt) | Platform environment |
| Production | Cloud PostgreSQL (production) | Platform environment |
Nếu DATABASE_URL rỗng, dùng local fallback. Nếu đã set, dùng connection được cung cấp.
Tại sao tách staging và production? Staging là sân tập. Không ai muốn test data lẫn vào production database, cũng không muốn vô tình break production trong khi testing.
Những lỗi hay mắc
Hardcode configuration -- cần trỏ API khác mà phải sửa code và redeploy. Branches khác nhau chỉ để phục vụ environments khác nhau. Extract hết configurable values thành environment variables đi.
Thiếu template -- developer mới vào hỏi "cần variables gì?" mà không ai trả lời được ngay. Tạo .env.example và update mỗi khi thêm/xóa variable.
Secrets trong git -- .env files trong git history, API keys nhìn thấy trong codebase. Nếu đã lỡ commit rồi thì rotate secrets ngay lập tức -- xóa file không đủ vì git history vẫn lưu.
Code theo environment name -- if (process.env.NODE_ENV === 'production') rải khắp nơi, bugs chỉ xuất hiện ở staging. Configure behavior qua environment variables, không phải environment names.
Thiếu validation -- runtime errors từ missing variables, features âm thầm fail, production issues khó debug. Validate hết lúc startup.
Local drift production -- "Works on my machine" nhưng production thì không. Dùng cùng database, cùng services, cùng setup locally. Docker giúp được chuyện này.
Đánh giá Environment Setup
Đang tốt nếu:
- Developers mới chạy được app với instructions rõ ràng
- Secrets không trong version control
- Thiếu configuration thì fail lúc startup với helpful errors
- Deploy tới bất kỳ environment nào mà không cần thay đổi code
- Local development giống production
Cần cải thiện nếu:
- Onboarding đòi hỏi hỏi miệng -- không có docs
- Developers share API keys qua chat
- Environment issues phát hiện ở production
- Environments khác nhau đòi hỏi code khác nhau
- Không chắc cái gì đang configure trong production
Checklist theo tình huống
Dự án mới
- Tạo
.env.examplevới tất cả variables documented - Thêm
.env.localvà.env*.localvào.gitignore - Setup validation lúc startup
- Document setup trong README
- Configure production secrets trong deployment platform
Thành viên mới join
- Copy
.env.examplethành.env.local - Điền required values (lấy từ team lead hoặc password manager)
- Cài local dependencies (database, etc.)
- Chạy app, verify nó start được
Thêm variable mới
- Thêm vào
.env.examplevới description - Thêm vào
.env.localcủa mình - Thêm validation nếu required
- Thêm vào production environment
- Deploy và verify
Thứ bậc quản lý secrets
Từ ít bảo mật đến bảo mật nhất:
- Plain text files -- không bao giờ cho secrets trong production
- Environment variables -- mức tối thiểu chấp nhận được
- Encrypted at rest -- platform secrets management
- Secrets management service -- Vault, AWS Secrets Manager
- Hardware security modules -- cho keys nhạy cảm nhất
Phù hợp mức bảo mật với độ nhạy cảm. Hầu hết apps chỉ cần platform environment variables. Data tài chính hoặc y tế có thể cần nhiều hơn.
Tham khảo nhanh
| Nguyên tắc | Implementation |
|---|---|
| Không bao giờ commit secrets | .gitignore, platform env |
| Document mọi thứ | .env.example |
| Validate lúc startup | Fail nhanh khi thiếu config |
| Cùng code, khác config | Environment variables |
| Development = Production | Docker, cùng services |