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ạiVí dụThay đổiBảo mật
SecretsAPI keysHiếm khiCao
EnvironmentDatabase URLTheo environmentTrung bình
Feature flagsEnable UI mớiThường xuyênThấp
ConstantsRetry countHiếm khiThấ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_KEY không phải SK

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

EnvironmentDatabaseSet qua
DevelopmentLocal SQLite hoặc Docker PostgreSQL.env.local hoặc default
StagingCloud PostgreSQL (tách biệt)Platform environment
ProductionCloud 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

  1. Tạo .env.example với tất cả variables documented
  2. Thêm .env.local.env*.local vào .gitignore
  3. Setup validation lúc startup
  4. Document setup trong README
  5. Configure production secrets trong deployment platform

Thành viên mới join

  1. Copy .env.example thành .env.local
  2. Điền required values (lấy từ team lead hoặc password manager)
  3. Cài local dependencies (database, etc.)
  4. Chạy app, verify nó start được

Thêm variable mới

  1. Thêm vào .env.example với description
  2. Thêm vào .env.local của mình
  3. Thêm validation nếu required
  4. Thêm vào production environment
  5. Deploy và verify

Thứ bậc quản lý secrets

Từ ít bảo mật đến bảo mật nhất:

  1. Plain text files -- không bao giờ cho secrets trong production
  2. Environment variables -- mức tối thiểu chấp nhận được
  3. Encrypted at rest -- platform secrets management
  4. Secrets management service -- Vault, AWS Secrets Manager
  5. 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ắcImplementation
Không bao giờ commit secrets.gitignore, platform env
Document mọi thứ.env.example
Validate lúc startupFail nhanh khi thiếu config
Cùng code, khác configEnvironment variables
Development = ProductionDocker, cùng services