Factor III — Config
Lưu config trong environment
Theo Twelve-Factor, config là mọi thứ có khả năng thay đổi giữa các deploy như thông tin kết nối database, credentials tới dịch vụ ngoài, hoặc hostname theo từng môi trường; còn code thì không nên đổi theo deploy.
- Factor III yêu cầu tách config ra khỏi code và lưu config trong environment variables vì chúng dễ thay đổi theo từng deploy, ít bị commit nhầm vào repo hơn config file, và không phụ thuộc ngôn ngữ hay framework.
- Twelve-Factor còn cảnh báo không nên gom config thành các “môi trường” kiểu
development,staging,productiontheo cách cứng nhắc; thay vào đó, từng env var nên là một nút điều khiển độc lập, quản lý riêng cho từng deploy. - Trong thực tế hiện đại, Kubernetes và Docker đều hỗ trợ đưa config vào container qua environment variables, ConfigMaps hoặc Secrets; đồng thời Docker cũng cảnh báo env vars trong container có thể lộ dưới dạng plain text, nên dữ liệu nhạy cảm thường cần đi qua cơ chế secrets của platform.
1) Mở bài: vì sao junior rất hay vấp ở chỗ này?
Rất nhiều bạn mới đi làm từng gặp một tình huống như thế này: code chạy ngon ở máy local, nhưng khi đẩy lên staging thì báo lỗi vì app đang dùng nhầm DB local, hoặc API key production lại bị hard-code trong source, hoặc file .env.production bị commit nhầm lên repo. Những lỗi kiểu này thường không phải do business logic khó, mà do config đang bị trộn với code hoặc đang được quản lý quá cảm tính. Factor III sinh ra để xử lý đúng nhóm vấn đề đó.
2) Định nghĩa gốc của Factor III
Tên chính thức của factor này là:
“Store config in the environment.”
Tài liệu gốc định nghĩa rất rõ: config của app là mọi thứ “likely to vary between deploys”, tức là những giá trị có thể khác nhau giữa staging, production, local của developer, hoặc các deploy khác. Ví dụ mà tài liệu gốc nêu luôn gồm: resource handles tới database, Memcached và các backing services khác; credentials tới dịch vụ ngoài như Amazon S3 hoặc Twitter; và các giá trị theo từng deploy như canonical hostname.
Một câu rất quan trọng của tài liệu gốc là: lưu config dưới dạng constants trong code là vi phạm Twelve-Factor, vì config thay đổi rất nhiều giữa các deploy, còn code thì không.
3) “Config” ở đây là gì, và không phải là gì?
Đây là chỗ junior rất dễ hiểu lầm.
Cái gì được tính là config?
Những thứ như:
DATABASE_URLREDIS_URLS3_BUCKETAPI_KEY- hostname của từng deploy
- cờ bật/tắt một tích hợp theo từng môi trường
Đều đúng tinh thần “config”, vì chúng có thể thay đổi giữa các deploy.
Cái gì không được tính là config theo nghĩa của factor này?
Tài liệu gốc nói rõ: những cấu hình nội bộ của ứng dụng như config/routes.rb trong Rails hoặc cách các module trong ứng dụng được nối với nhau không được tính là config kiểu Twelve-Factor, vì chúng không thay đổi giữa các deploy; những thứ đó nên nằm trong code.
Nói dễ hiểu hơn:
Nếu một giá trị thay đổi khi bạn deploy sang nơi khác, nó có xu hướng là config.
Nếu nó là cách ứng dụng được tổ chức về mặt logic và gần như giữ nguyên ở mọi nơi, nó nên ở trong code.
4) Litmus test cực hay của Twelve-Factor
Tài liệu gốc đưa ra một bài test rất mạnh:
Codebase có thể open-source ngay lập tức mà không làm lộ credentials hay thông tin nhạy cảm không?
Nếu câu trả lời là “không”, thì nhiều khả năng config của bạn chưa được tách ra khỏi code đúng cách. Đây là một nguyên tắc rất dễ nhớ và cực kỳ thực dụng.
5) Vì sao Factor III lại quan trọng đến vậy?
Nó giải quyết ít nhất 4 vấn đề rất thật.
a) Giảm rủi ro lộ bí mật
Khi credentials nằm trong code hoặc trong config file dễ commit nhầm, nguy cơ rò rỉ tăng mạnh. Twelve-Factor ưu tiên env vars một phần vì chúng ít có khả năng bị check vào repo hơn config files.
b) Cho phép cùng một code chạy ở nhiều nơi
Config thay đổi theo deploy, code thì giữ nguyên. Điều này giúp cùng một build hoặc cùng một codebase có thể chạy ở local, test, staging, production mà không phải sửa source. Kubernetes còn mô tả ConfigMaps theo đúng tinh thần này: tách configuration artifacts khỏi image content để giữ ứng dụng containerized portable, và cùng một image có thể chạy cho local dev, system test hoặc live workload.
c) Giảm “máy em chạy được”
Khi giá trị cấu hình được cấp vào lúc chạy thay vì cắm cứng trong source, team dễ hiểu hệ thống hơn: cái gì là code, cái gì là per-deploy setting. Twelve-Factor cũng nhấn mạnh sự tách biệt sạch giữa codebase và config là một nền tảng hữu ích cho các workflow hiện đại như GitOps.
d) Scale số lượng deploy tốt hơn
Tài liệu gốc cảnh báo nếu bạn gom config thành các nhóm tên môi trường như development, test, production, rồi sau đó thêm staging, qa, joes-staging, hệ thống sẽ rơi vào “combinatorial explosion” rất brittle. Twelve-Factor muốn mỗi env var là một biến độc lập, quản lý riêng cho từng deploy.
6) Vì sao Twelve-Factor thích environment variables?
Tài liệu gốc đưa ra 3 lý do chính:
- Env vars dễ thay đổi giữa các deploy mà không cần đổi code.
- So với config files, env vars ít có khả năng bị check nhầm vào repo hơn.
- Env vars là chuẩn không phụ thuộc ngôn ngữ hay OS, trong khi custom config files hoặc Java System Properties có thể mang tính framework/language-specific hơn.
Nói theo kiểu đời thường: env vars giống như các “núm chỉnh bên ngoài” của ứng dụng. Bạn giữ cái máy giống nhau, chỉ chỉnh các núm theo nơi nó đang chạy.
7) Ví dụ cực dễ hiểu
Hãy tưởng tượng bạn có một máy pha cà phê.
- Code là bản thiết kế của cái máy.
- Config là điện áp đầu vào, lượng nước mặc định, ngôn ngữ hiển thị, loại hạt đang dùng ở từng cửa hàng.
Nếu bạn mang cùng một máy sang chi nhánh khác, bạn không nên phải hàn lại bo mạch chỉ để đổi ngôn ngữ hiển thị hay điện áp. Bạn chỉ cần đổi “thiết lập vận hành” bên ngoài. Đó chính là tinh thần của Factor III.
8) Ví dụ xuyên suốt với ứng dụng mẫu TaskFlow
Ta tiếp tục dùng ứng dụng mẫu TaskFlow.
TaskFlow có các giá trị sau:
DATABASE_URLREDIS_URLSMTP_HOSTSMTP_USERSMTP_PASSWORDAPP_BASE_URLPAYMENT_PROVIDER_API_KEY
Cách làm đúng
Code của TaskFlow không chứa cứng bất kỳ giá trị nào ở trên. Khi chạy local, staging hay production, app chỉ đọc các giá trị từ environment lúc khởi động. Nhờ đó, cùng một code có thể chạy ở nhiều deploy khác nhau mà không phải sửa logic ứng dụng. Cách làm này đúng với định nghĩa config của Twelve-Factor.
Cách làm sai
- Hard-code
postgres://prod-db...trong source. - Có
config.production.jschứa credentials rồi commit vào repo. - Dùng một biến kiểu
APP_ENV=productionrồi bên trong code if/else hàng loạt cho đủ loại hành vi khác nhau.
Phần sai nhất ở đây không chỉ là “lộ password”, mà còn là bạn đang làm cho config và code dính chặt vào nhau, khiến mỗi deploy trở thành một biến thể khó kiểm soát. Twelve-Factor coi config files không check vào revision control là một bước tiến hơn hard-coded constants, nhưng vẫn có nhược điểm: dễ bị check nhầm vào repo, dễ bị rải rác khắp nơi và thường phụ thuộc framework.
9) Junior thường hiểu sai ở đâu?
Hiểu sai 1: “Config là mọi file trong thư mục config”
Không đúng. Theo Twelve-Factor, chỉ những gì thay đổi giữa các deploy mới là config theo nghĩa của factor này. Routes, wiring nội bộ, module composition thường không nằm trong nhóm đó.
Hiểu sai 2: “Chỉ cần có một file .env là xong”
.env có thể là công cụ tiện lúc local, nhưng điều cốt lõi của factor không phải là “xài file .env”, mà là tách config khỏi code và quản lý nó theo từng deploy. Docker Compose đúng là hỗ trợ environment và env_file, còn Kubernetes cho phép dùng env hoặc envFrom để nạp từ ConfigMap hay Secret, nhưng đó là cách triển khai cụ thể của platform chứ không phải toàn bộ tinh thần của factor.
Hiểu sai 3: “Cứ gom thành dev, staging, prod là ổn”
Tài liệu gốc nói cách batch config thành named environments không scale tốt, vì số deploy tăng lên sẽ sinh thêm quá nhiều tổ hợp và làm quản lý config brittle. Twelve-Factor muốn các env vars là các control độc lập, không bị khóa cứng vào một nhóm môi trường.
Hiểu sai 4: “Secrets cứ để env vars là an toàn”
Đây là chỗ cần nói thật cẩn thận. Twelve-Factor gốc ưu tiên env vars vì tính portable và tách biệt khỏi code. Nhưng trong môi trường container hiện đại, Docker Compose khuyên không dùng environment variables để đưa thông tin nhạy cảm như password vào container, mà nên dùng secrets; Docker CLI docs cũng cảnh báo env vars trong cấu hình container được lưu dưới dạng plain text và có thể bị inspect qua API hoặc bị commit vào image khi dùng docker commit. Kubernetes thì hỗ trợ cấp dữ liệu nhạy cảm bằng Secret, bao gồm cả việc inject vào env vars nếu cần.
10) Case thực tế đã được xác minh
Kubernetes: tách config khỏi image
Kubernetes mô tả ConfigMap là cơ chế để inject configuration data vào Pods, và nhấn mạnh mục tiêu là decouple configuration artifacts from image content để cùng một container image có thể chạy cho local development, system test hoặc live workload. Đây là một ví dụ hiện đại rất gần với tinh thần “code giữ nguyên, config thay theo deploy.”
Kubernetes: inject env vars trực tiếp hoặc từ ConfigMap/Secret
Tài liệu chính thức của Kubernetes cho biết trong Pod spec, bạn có thể dùng env hoặc envFrom; env cho phép set trực tiếp từng biến, còn envFrom cho phép nạp toàn bộ key-value pairs từ ConfigMap hoặc Secret thành environment variables trong container.
Docker Compose: quản lý env vars và tách dữ liệu nhạy cảm
Docker Compose cho phép cấu hình env vars qua environment, env_file, hoặc override tạm thời qua docker compose run -e. Đồng thời, docs cũng nói rõ không nên dùng env vars để đưa thông tin nhạy cảm như passwords vào container; nên dùng secrets thay thế.
Những ví dụ này không thay đổi nội dung gốc của Factor III; chúng là cách các platform hiện đại hiện thực hóa hoặc mở rộng nguyên tắc “separate code from config.” Twelve-Factor blog năm 2025 cũng mô tả methodology này hỗ trợ GitOps bằng cách tạo ra ranh giới sạch giữa codebase và config, hỗ trợ environment-specific settings và automated deployments.
11) Làm sai thì hậu quả gì?
- Lộ credentials vì commit nhầm file cấu hình hoặc hard-code vào source.
- Cùng một app nhưng staging và production cư xử khác nhau theo những cách team không nhìn thấy rõ.
- Deploy mới sinh ra thêm một nhánh config đặc biệt kiểu
qa2,hotfix-prod,linh-local, khiến quản lý ngày càng brittle. - Container image không còn portable vì image đã “nướng sẵn” config vào bên trong thay vì nhận config từ runtime. Kubernetes mô tả rõ rằng việc tách config khỏi image content giúp giữ tính portable của workload containerized.
12) Áp dụng đúng trong team như thế nào?
Bước 1: Phân loại rõ code và config
Mỗi giá trị trong app hãy tự hỏi:
- Nó có đổi giữa local, staging, production không?
- Nếu deploy sang tenant khác hay region khác, nó có đổi không?
Nếu có, nhiều khả năng đó là config. Nếu không, có lẽ nó thuộc về code. Đây chính là ranh giới mà Factor III yêu cầu.
Bước 2: Đưa per-deploy values ra khỏi source code
DB URL, API keys, hostname, bucket names, feature toggles theo deploy nên được cấp từ environment hoặc cơ chế runtime tương đương của platform. Kubernetes cho phép làm việc này qua env, envFrom, ConfigMap và Secret. Docker Compose cho phép qua environment hoặc env_file.
Bước 3: Tránh “một biến tổng” kiểu APP_ENV xử lý tất cả
Tài liệu gốc khuyên env vars nên là các control orthogonal với nhau, không nên đóng gói thành các “environment” cứng. Nghĩa là thay vì dựa quá nhiều vào if APP_ENV == "prod", bạn nên có các biến cấu hình độc lập như LOG_LEVEL, CACHE_ENABLED, BASE_URL, PAYMENT_MODE, v.v.
Bước 4: Với dữ liệu nhạy cảm, dùng secret mechanism của platform
Trong môi trường container, docs của Docker cảnh báo env vars có thể lộ ở dạng plain text; Compose cũng bảo nên dùng secrets cho passwords. Vì vậy, với password, tokens, private keys, team nên ưu tiên secrets manager hoặc secret primitives của platform, rồi inject vào runtime bằng cách phù hợp. Kubernetes chính thức hỗ trợ Secret và cả cách dùng Secret như environment variables.
Bước 5: Giữ cùng một build, đổi config theo deploy
Đây là một thực hành rất đẹp: build artifact hoặc container image giữ nguyên, còn khác biệt giữa local, staging, production nằm ở config lúc release/run. Twelve-Factor blog 2025 cũng nhấn mạnh lợi ích của separation giữa codebase, config và build/release/run stages trong các platform hiện đại.
13) Diễn giải trong bối cảnh hiện đại
Có một nuance rất đáng biết: nội dung gốc của Factor III vẫn nói khá dứt khoát là store config in the environment.
Nhưng trong bối cảnh cloud-native, cộng đồng Twelve-Factor cũng đang thảo luận việc cập nhật factor này để cho phép mounted volumes như một cách xử lý config phù hợp hơn với một số môi trường hiện đại; tính đến bản cập nhật tháng 12 năm 2024, đây vẫn là proposal đang được thảo luận, chưa phải nội dung gốc đã được thay thế.
Vì vậy, cách hiểu an toàn là:
- Nguyên lý gốc: config phải tách khỏi code và thay đổi theo deploy, ưu tiên env vars.
- Diễn giải hiện đại: platform có thể cung cấp config/secrets qua env vars, secrets, config objects, thậm chí mounted files, miễn là vẫn giữ ranh giới sạch giữa code và per-deploy config.
14) Checklist tự đánh giá
Hãy thử tự hỏi về app của bạn:
- Codebase có thể open-source ngay mà không làm lộ credentials không?
- Các giá trị như DB URL, API keys, hostname có đang nằm trong source không?
- Build artifact hoặc container image của bạn có thể giữ nguyên giữa các deploy không?
- Team có đang thêm vô số “môi trường tên riêng” kiểu
qa2,abc-staging,minh-devkhông? - Secrets có đang bị nhét vào env vars/container theo cách dễ bị lộ không?
Nếu có từ 2 câu trở lên khiến bạn thấy lấn cấn, Factor III của hệ thống đó đang cần chỉnh lại.
15) Tóm tắt nhanh
- Config là mọi thứ thay đổi giữa các deploy; code thì không nên thay đổi theo deploy.
- Twelve-Factor yêu cầu tách config khỏi code và lưu config trong environment.
- Không nên gom config thành các “environment groups” cứng nhắc; từng biến nên độc lập và quản lý riêng cho từng deploy.
- Trong thực tế hiện đại, Kubernetes và Docker cung cấp nhiều cơ chế để inject config/secrets vào runtime, nhưng tinh thần cốt lõi vẫn là: code và per-deploy config phải tách bạch.
19) Nguồn tham khảo
- The Twelve-Factor App — III. Config.
- Kubernetes Docs — Define Environment Variables for a Container.
- Kubernetes Docs — Configure a Pod to Use a ConfigMap.
- Kubernetes Docs — Secrets.
- Docker Docs — Set environment variables within your container’s environment.
- Docker Docs —
dockerCLI reference warning on environment variables as plain text. - Twelve-Factor Blog — Applications to Modern Cloud-Native Platforms.
- Twelve-Factor Blog — December Monthly Updates.