Biến môi trường là các giá trị động được truyền cho ứng dụng khi khởi động để cấu hình hành vi mà không cần thay đổi mã. Chúng cho phép tách biệt cấu hình phát triển, kiểm thử và sản xuất. Theo Twelve-Factor App, 2025, cấu hình nên được lưu trữ trong biến môi trường, không phải trong mã. Biến môi trường đảm bảo quản lý an toàn các khóa API, URL backend và cờ tính năng.
Những điểm chính
Biến môi trường là cặp khóa-giá trị có thể truy cập được cho tiến trình ứng dụng thông qua API hệ điều hành. Chúng được truyền cho tiến trình khi nó được tạo và chỉ tồn tại trong thời gian chạy của nó. Không giống như các tham số cấu hình được nhúng trong mã nguồn, biến môi trường không yêu cầu biên dịch lại để thay đổi giá trị. Đây là nguyên tắc cơ bản của Twelve-Factor App, đảm bảo sự tách biệt rõ ràng giữa mã và cấu hình.
Trong phát triển di động, biến môi trường giải quyết vấn đề cấu hình khác nhau cho các môi trường: nhà phát triển sử dụng máy chủ cục bộ, người kiểm thử sử dụng staging và người dùng sử dụng sản xuất. Thay vì lưu trữ ba URL backend trong mã với các câu lệnh điều kiện if-else, nhà phát triển truyền một URL qua biến môi trường tại thời điểm xây dựng. Điều này đơn giản hóa mã và loại bỏ rủi ro vô tình sử dụng máy chủ sản xuất trong môi trường kiểm thử.
Lợi thế chính là bảo mật: dữ liệu nhạy cảm không lọt vào kho mã. Khóa API, bí mật Firebase, mã thông báo truy cập backend và chứng chỉ được tải qua CI/CD trực tiếp vào môi trường xây dựng. Nếu kẻ tấn công truy cập được vào kho mã, chúng sẽ không tìm thấy bí mật ở đó, vì chúng được lưu trữ trong kho được bảo vệ của hệ thống CI và chỉ được truyền ở giai đoạn xây dựng tệp nhị phân.
Các dự án di động có ít nhất ba môi trường: phát triển, staging và sản xuất. Mỗi môi trường yêu cầu bộ cấu hình riêng: URL máy chủ, tên gói, lược đồ ký và chứng chỉ thông báo đẩy. Nếu không có biến môi trường, nhà phát triển phải thay đổi cấu hình thủ công trước mỗi lần xây dựng, dẫn đến lỗi: khóa sản xuất bị quên trong bản dựng kiểm thử có thể gửi thông báo cho người dùng thực hoặc tiêu thụ API trả phí.
Biến môi trường cho phép chuyển đổi backend mà không cần thay đổi mã: chỉ cần thay đổi giá trị trong biến API_BASE_URL. Cờ tính năng được quản lý qua các biến như FEATURE_CHAT_ENABLED=true, cho phép bật các tính năng mới trong staging mà không ảnh hưởng đến sản xuất. Mỗi môi trường có tệp .env riêng được tải tại thời điểm xây dựng.
class AppConfig {
static final String apiBaseUrl =
const String.fromEnvironment('API_BASE_URL',
defaultValue: 'http://localhost:8080');
}
Khóa được mã hóa cứng là một lỗ hổng phổ biến trong ứng dụng di động. Kẻ tấn công dịch ngược APK hoặc IPA bằng các công cụ như jadx hoặc Hopper và trích xuất bí mật từ tệp nhị phân. Ngay cả làm rối mã cũng không bảo vệ được các chuỗi ký tự — chúng dễ dàng bị tìm thấy trong mã sau khi dịch ngược. Biến môi trường giải quyết vấn đề này bằng cách truyền khóa tại thời điểm xây dựng qua CI/CD, nơi chúng bị che trong nhật ký.
object Config {
val apiKey: String =
System.getenv("API_KEY") ?: throw
IllegalStateException("API_KEY not set")
}
Biến môi trường tích hợp với các đường ống xây dựng: GitHub Actions, GitLab CI, Bitrise và CircleCI hỗ trợ biến bí mật không hiển thị trong nhật ký. Tại thời điểm xây dựng, CI thay thế các giá trị thích hợp tùy theo nhánh hoặc thẻ: cho nhánh develop sử dụng staging, cho thẻ v* sử dụng sản xuất. Điều này tự động hóa quy trình và loại bỏ yếu tố con người, đảm bảo mỗi bản dựng nhận được bộ cấu hình chính xác.
Tệp .env là cách tiêu chuẩn để lưu trữ biến môi trường ở định dạng KEY=VALUE. Nó không được bao gồm trong kho lưu trữ; thay vào đó, .env.example được thêm vào với một mẫu của tất cả các biến và giá trị trống. Mỗi nhà phát triển tạo tệp .env riêng với cài đặt cục bộ mà không ảnh hưởng đến cấu hình của các thành viên khác trong nhóm. Các tệp riêng biệt được sử dụng cho các môi trường khác nhau: .env.dev, .env.stage, .env.prod.
# .env.example — mẫu cho nhà phát triển
API_BASE_URL=http://localhost:8080
FEATURE_CHAT_ENABLED=true
SENTRY_DSN=
Đối với các dự án di động, có các thư viện chuyên dụng để làm việc với tệp .env:
Cài đặt rẽ nhánh trong CI/CD cho phép thay thế các tệp .env khác nhau: .env.dev cho máy chủ kiểm thử, .env.stage cho tiền phát hành và .env.prod cho xuất bản lên cửa hàng ứng dụng. Các tệp có bí mật được tải từ kho lưu trữ an toàn (Vault, AWS Secrets Manager) và không được lưu trữ trong kho lưu trữ. Điều này đảm bảo rằng ngay cả khi hệ thống kiểm soát phiên bản bị xâm phạm, các bí mật vẫn được bảo vệ.
Hệ sinh thái iOS sử dụng tệp xcconfig để quản lý biến ở cấp độ xây dựng. Chúng được đính kèm vào lược đồ Xcode và cho phép ghi đè giá trị cho cấu hình Debug và Release. Tệp xcconfig hỗ trợ kế thừa: bạn có thể tạo tệp cơ sở với cài đặt chung và các tệp cụ thể cho từng môi trường.
Tệp xcconfig lưu trữ biến ở định dạng KEY = VALUE và được đính kèm vào lược đồ xây dựng trong Xcode qua cài đặt Configuration. Các biến từ xcconfig có sẵn trong Info.plist qua cú pháp $(TÊN_BIẾN), cho phép các định danh gói và tên ứng dụng khác nhau cho các lược đồ khác nhau. Để nhận dạng môi trường nhanh chóng, hậu tố Dev hoặc Staging được thêm vào tên ứng dụng.
# Config/Dev.xcconfig — cấu hình phát triển
API_BASE_URL = http://localhost:3000
BUNDLE_ID_SUFFIX = .dev
APP_DISPLAY_NAME = MyApp Dev
Để truy cập biến tại thời gian chạy trong iOS, tệp Configuration.swift được sử dụng, đọc giá trị từ Info.plist qua Bundle.main.object(forInfoDictionaryKey:). Cách tiếp cận này đảm bảo rằng các biến được xác định tại thời điểm xây dựng và có sẵn cho ứng dụng ngay sau khi khởi động. Các giá trị được đọc một lần trong quá trình khởi tạo mô-đun và được lưu vào bộ nhớ đệm để truy cập nhanh trong suốt vòng đời ứng dụng.
enum AppEnvironment {
static var apiBaseURL: URL {
guard let urlString = Bundle.main
.object(forInfoDictionaryKey: "API_BASE_URL"),
let url = URL(string: urlString as! String)
else { fatalError("API_BASE_URL is not configured") }
return url
}
static var isChatEnabled: Bool {
Bundle.main.object(
forInfoDictionaryKey: "FEATURE_CHAT_ENABLED"
) as? Bool ?? false
}
}
Android hỗ trợ biến môi trường qua BuildConfig — một lớp được tạo tự động có các trường được xác định trong tệp build.gradle của mô-đun. BuildConfig được tạo tại thời điểm biên dịch cho mỗi flavor và loại xây dựng riêng biệt. Điều này cho phép có các giá trị khác nhau cho debug và release mà không cần sử dụng toán tử điều kiện trong mã, cải thiện hiệu suất và bảo mật.
Các trường BuildConfig được đặt qua buildConfigField trong defaultConfig hoặc trong các buildTypes cụ thể. Một buildType hoặc productFlavor riêng được tạo cho mỗi môi trường. Điều này đảm bảo sự cô lập cấu hình nghiêm ngặt: debug sử dụng máy chủ cục bộ, release sử dụng sản xuất. Các trường BuildConfig được định kiểu tĩnh, loại bỏ lỗi khi truy cập chúng trong mã.
// build.gradle (Module: app)
android {
defaultConfig {
buildConfigField "String", "API_BASE_URL",
"\"http://localhost:8080\""
}
buildTypes {
debug {
buildConfigField "String", "API_BASE_URL",
"\"http://dev.api.itsectr.com\""
}
release {
buildConfigField "String", "API_BASE_URL",
"\"https://api.itsectr.com\""
}
}
}
Tệp gradle.properties trong thư mục gốc dự án lưu trữ các biến Gradle toàn cục. Chúng có sẵn trong tất cả các mô-đun qua cú pháp $variableName và được sử dụng để chỉ định phiên bản phụ thuộc, cờ xây dựng và khóa API. Không giống như BuildConfig, gradle.properties chỉ hoạt động ở giai đoạn cấu hình Gradle, không phải tại thời gian chạy của ứng dụng. Do đó, mật khẩu và khóa API được chỉ định trong gradle.properties không hiển thị trong mã đã dịch ngược, vì chúng chỉ được sử dụng để tạo BuildConfig tại thời điểm biên dịch.
# gradle.properties
SENTRY_DSN=https://key@sentry.io/project
MAPS_API_KEY=AIzaSy...
Để truyền bí mật an toàn trong các dự án Android, nên sử dụng local.properties (bị loại khỏi VCS) hoặc tải giá trị từ biến CI/CD trong build.gradle qua System.getenv(). Điều này đảm bảo rằng các khóa không lọt vào kho lưu trữ. Khi xuất bản lên Google Play Console, hãy đảm bảo rằng tất cả các khóa gỡ lỗi được thay thế bằng phiên bản sản xuất qua các buildTypes hoặc productFlavors khác nhau với các giá trị BuildConfig tương ứng.
Câu hỏi thường gặp
Có, Flutter hỗ trợ biến môi trường qua gói flutter_dotenv để truy cập thời gian chạy hoặc qua kênh gốc cho biến nền tảng. Dart cũng có hàm tạo String.fromEnvironment để truyền giá trị tại thời điểm biên dịch qua --dart-define, đây là cách ưa thích cho các dự án Flutter.
BuildConfig là một lớp Java với các trường được định kiểu được tạo tại thời điểm biên dịch cho mỗi buildType và flavor. gradle.properties là một tệp văn bản với các cặp khóa-giá trị có sẵn cho tất cả các mô-đun Gradle ở giai đoạn cấu hình xây dựng. BuildConfig hoạt động tại thời gian chạy của ứng dụng, gradle.properties — chỉ trong tập lệnh Gradle.
Thêm .env vào tệp .gitignore của kho lưu trữ. Trong kho lưu trữ, chỉ cam kết .env.example với các giá trị trống và mô tả của từng biến. Đối với CI/CD, hãy sử dụng bí mật được mã hóa trong cài đặt GitHub Actions, GitLab CI hoặc Bitrise, được che trong nhật ký và không có sẵn để đọc sau khi hoàn tất xây dựng.
Hầu hết các hệ thống CI đều hỗ trợ biến môi trường bí mật. Trong GitHub Actions đó là Secrets, trong GitLab CI — CI/CD Variables, trong Bitrise — Secrets. Tại thời điểm xây dựng, chúng được truyền đến tập lệnh xây dựng qua process.env hoặc System.getenv(). Các biến bí mật không hiển thị trong nhật ký xây dựng và không có sẵn trong các nhánh rẽ của kho lưu trữ.
Cờ tính năng là các biến boolean kiểm soát việc bật hoặc tắt chức năng mà không cần biên dịch lại mã. Ví dụ: FEATURE_NEW_PAYMENT=true bật hệ thống thanh toán mới trong staging để kiểm thử. Trong sản xuất, cùng một cờ được đặt thành false cho đến khi backend được triển khai đầy đủ. Điều này cho phép triển khai các thay đổi một cách an toàn theo từng giai đoạn và thu hồi chúng nếu có sự cố.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm