환경 변수는 코드를 변경하지 않고 동작을 구성하기 위해 애플리케이션 시작 시 전달되는 동적 값입니다. 개발, 테스트, 프로덕션 구성을 분리할 수 있습니다. Twelve-Factor App, 2025에 따르면 구성은 코드가 아닌 환경 변수에 저장해야 합니다. 환경 변수는 API 키, 백엔드 URL 및 기능 플래그의 안전한 관리를 보장합니다.
핵심 요점
환경 변수는 운영 체제 API를 통해 애플리케이션 프로세스가 액세스할 수 있는 키-값 쌍입니다. 프로세스가 생성될 때 전달되며 실행 중에만 존재합니다. 소스 코드에 포함된 구성 매개변수와 달리 환경 변수는 값을 변경하기 위해 재컴파일이 필요하지 않습니다. 이것은 Twelve-Factor App의 기본 원칙으로, 코드와 구성 간의 명확한 분리를 보장합니다.
모바일 개발에서 환경 변수는 다양한 환경에 대한 구성 문제를 해결합니다. 개발자는 로컬 서버를, 테스터는 스테이징을, 사용자는 프로덕션을 사용합니다. if-else 조건문과 함께 코드에 세 개의 백엔드 URL을 저장하는 대신, 개발자는 빌드 시간에 환경 변수를 통해 하나의 URL을 전달합니다. 이렇게 하면 코드가 단순화되고 테스트 환경에서 실수로 프로덕션 서버를 사용할 위험이 제거됩니다.
주요 이점은 보안입니다. 민감한 데이터가 코드 리포지토리로 유출되지 않습니다. API 키, Firebase 시크릿, 백엔드 액세스 토큰 및 인증서는 CI/CD를 통해 빌드 환경에 직접 로드됩니다. 공격자가 코드 리포지토리에 액세스하더라도 시크릿을 찾을 수 없습니다. CI 시스템의 보호된 저장소에 저장되며 바이너리 파일 빌드 단계에서만 전달되기 때문입니다.
모바일 프로젝트에는 최소한 세 가지 환경(개발, 스테이징, 프로덕션)이 있습니다. 각 환경에는 고유한 구성 세트(서버 URL, 패키지 이름, 서명 체계, 푸시 알림 인증서)가 필요합니다. 환경 변수가 없으면 개발자는 빌드할 때마다 수동으로 구성을 변경해야 하며, 이로 인해 오류가 발생합니다. 테스트 빌드에서 프로덕션 키를 잊어버리면 실제 사용자에게 알림이 전송되거나 유료 API가 소비될 수 있습니다.
환경 변수를 사용하면 코드를 변경하지 않고 백엔드를 전환할 수 있습니다. API_BASE_URL 변수의 값만 변경하면 됩니다. 기능 플래그는 FEATURE_CHAT_ENABLED=true와 같은 변수를 통해 관리되며, 프로덕션에 영향을 주지 않고 스테이징에서 새 기능을 활성화할 수 있습니다. 각 환경에는 빌드 시 로드되는 자체 .env 파일이 있습니다.
class AppConfig {
static final String apiBaseUrl =
const String.fromEnvironment('API_BASE_URL',
defaultValue: 'http://localhost:8080');
}
하드코딩된 키는 모바일 애플리케이션의 일반적인 취약점입니다. 공격자는 jadx 또는 Hopper와 같은 도구를 사용하여 APK 또는 IPA를 디컴파일하고 바이너리 파일에서 시크릿을 추출합니다. 난독화조차 문자열 리터럴을 보호하지 못합니다. 디컴파일 후 코드에서 쉽게 찾을 수 있습니다. 환경 변수는 CI/CD를 통해 빌드 시간에 키를 전달하여 이 문제를 해결하며, 로그에서 마스킹됩니다.
object Config {
val apiKey: String =
System.getenv("API_KEY") ?: throw
IllegalStateException("API_KEY not set")
}
환경 변수는 빌드 파이프라인과 통합됩니다. GitHub Actions, GitLab CI, Bitrise 및 CircleCI는 로그에 표시되지 않는 비밀 변수를 지원합니다. 빌드 시 CI는 브랜치 또는 태그에 따라 적절한 값을 대체합니다. develop 브랜치에는 스테이징이, v* 태그에는 프로덕션이 사용됩니다. 이는 프로세스를 자동화하고 인적 요소를 제거하여 각 빌드가 올바른 구성 세트를 받도록 보장합니다.
.env 파일은 KEY=VALUE 형식으로 환경 변수를 저장하는 표준 방법입니다. 리포지토리에 포함되지 않으며, 대신 모든 변수의 템플릿과 빈 값이 포함된 .env.example이 추가됩니다. 각 개발자는 팀원의 구성에 영향을 주지 않고 로컬 설정으로 자신의 .env 파일을 만듭니다. 다른 환경에는 별도의 파일이 사용됩니다: .env.dev, .env.stage, .env.prod.
# .env.example — 개발자용 템플릿
API_BASE_URL=http://localhost:8080
FEATURE_CHAT_ENABLED=true
SENTRY_DSN=
모바일 프로젝트를 위해 .env 파일 작업을 위한 전문 라이브러리가 있습니다:
CI/CD의 브랜칭 설정을 통해 다른 .env 파일을 대체할 수 있습니다: 테스트 서버용 .env.dev, 사전 릴리스용 .env.stage, 앱 스토어 게시용 .env.prod. 시크릿이 포함된 파일은 안전한 저장소(Vault, AWS Secrets Manager)에서 로드되며 리포지토리에 저장되지 않습니다. 이는 버전 관리 시스템이 손상되더라도 시크릿이 보호되도록 보장합니다.
iOS 생태계는 빌드 수준에서 변수를 관리하기 위해 xcconfig 파일을 사용합니다. 이 파일은 Xcode 스키마에 연결되며 Debug 및 Release 구성 값을 재정의할 수 있습니다. xcconfig 파일은 상속을 지원합니다: 공통 설정이 포함된 기본 파일과 각 환경에 대한 특정 파일을 만들 수 있습니다.
xcconfig 파일은 KEY = VALUE 형식으로 변수를 저장하고 Configuration 설정을 통해 Xcode의 빌드 스키마에 연결됩니다. xcconfig의 변수는 $(VARIABLE_NAME) 구문을 통해 Info.plist에서 사용할 수 있어, 다른 스키마에 다른 번들 식별자와 앱 이름을 사용할 수 있습니다. 빠른 환경 식별을 위해 앱 이름에 Dev 또는 Staging 접미사가 추가됩니다.
# Config/Dev.xcconfig — 개발 구성
API_BASE_URL = http://localhost:3000
BUNDLE_ID_SUFFIX = .dev
APP_DISPLAY_NAME = MyApp Dev
iOS에서 런타임에 변수에 액세스하려면 Configuration.swift 파일을 사용하여 Bundle.main.object(forInfoDictionaryKey:)를 통해 Info.plist에서 값을 읽습니다. 이 접근 방식은 변수가 빌드 시 정의되고 시작 직후 애플리케이션에서 사용할 수 있도록 보장합니다. 값은 모듈 초기화 중에 한 번 읽히고 애플리케이션 수명 주기 동안 빠른 액세스를 위해 캐시됩니다.
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는 BuildConfig를 통해 환경 변수를 지원합니다. 모듈의 build.gradle 파일에서 필드가 정의되는 자동 생성 클래스입니다. BuildConfig는 각 flavor 및 빌드 유형에 대해 컴파일 시 별도로 생성됩니다. 이렇게 하면 코드에서 조건 연산자를 사용하지 않고 디버그와 릴리스에 대해 다른 값을 가질 수 있어 성능과 보안이 향상됩니다.
BuildConfig 필드는 defaultConfig 또는 특정 buildTypes에서 buildConfigField를 통해 설정됩니다. 각 환경에 대해 별도의 buildType 또는 productFlavor가 생성됩니다. 이는 엄격한 구성 격리를 보장합니다: 디버그는 로컬 서버를 사용하고 릴리스는 프로덕션을 사용합니다. BuildConfig 필드는 정적으로 유형화되어 코드에서 액세스할 때 오류를 제거합니다.
// 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\""
}
}
}
프로젝트 루트의 gradle.properties 파일은 전역 Gradle 변수를 저장합니다. $variableName 구문을 통해 모든 모듈에서 사용할 수 있으며 종속성 버전, 빌드 플래그 및 API 키를 지정하는 데 사용됩니다. BuildConfig와 달리 gradle.properties는 Gradle 구성 단계에서만 작동하며 애플리케이션 런타임에서는 작동하지 않습니다. 따라서 gradle.properties에 지정된 비밀번호와 API 키는 디컴파일된 코드에 표시되지 않습니다. 컴파일 시 BuildConfig를 생성하는 데만 사용되기 때문입니다.
# gradle.properties
SENTRY_DSN=https://key@sentry.io/project
MAPS_API_KEY=AIzaSy...
Android 프로젝트에서 시크릿을 안전하게 전송하려면 local.properties(VCS에서 제외)를 사용하거나 System.getenv()를 통해 build.gradle의 CI/CD 변수에서 값을 로드하는 것이 좋습니다. 이렇게 하면 키가 리포지토리로 유출되지 않습니다. Google Play Console에 게시할 때는 모든 디버그 키가 해당 BuildConfig 값을 가진 다른 buildTypes 또는 productFlavors를 통해 프로덕션 버전으로 대체되었는지 확인하십시오.
자주 묻는 질문
네, Flutter는 런타임 액세스를 위한 flutter_dotenv 패키지 또는 플랫폼 변수를 위한 네이티브 채널을 통해 환경 변수를 지원합니다. Dart는 --dart-define을 통해 컴파일 시 값을 전달하는 String.fromEnvironment 생성자도 제공하며, 이는 Flutter 프로젝트에서 선호되는 방법입니다.
BuildConfig는 각 buildType 및 flavor에 대해 컴파일 시 생성되는 유형화된 필드를 가진 Java 클래스입니다. gradle.properties는 빌드 구성 단계에서 모든 Gradle 모듈이 액세스할 수 있는 키-값 쌍이 포함된 텍스트 파일입니다. BuildConfig는 애플리케이션 런타임에 작동하고 gradle.properties는 Gradle 스크립트에서만 작동합니다.
리포지토리의 .gitignore 파일에 .env를 추가하세요. 리포지토리에는 빈 값과 각 변수에 대한 설명이 포함된 .env.example만 커밋하세요. CI/CD의 경우 GitHub Actions, GitLab CI 또는 Bitrise 설정에서 암호화된 시크릿을 사용하세요. 로그에서 마스킹되며 빌드 완료 후 읽을 수 없습니다.
대부분의 CI 시스템은 비밀 환경 변수를 지원합니다. GitHub Actions에서는 Secrets, GitLab CI에서는 CI/CD Variables, Bitrise에서는 Secrets입니다. 빌드 시 process.env 또는 System.getenv()를 통해 빌드 스크립트로 전달됩니다. 비밀 변수는 빌드 로그에 표시되지 않으며 리포지토리 포크에서 사용할 수 없습니다.
기능 플래그는 코드를 재컴파일하지 않고 기능 활성화 또는 비활성화를 제어하는 부울 변수입니다. 예: FEATURE_NEW_PAYMENT=true는 테스트를 위해 스테이징에서 새 결제 시스템을 활성화합니다. 프로덕션에서는 백엔드가 완전히 배포될 때까지 동일한 플래그가 false로 설정됩니다. 이를 통해 변경 사항을 단계적으로 안전하게 롤아웃하고 문제가 발생하면 롤백할 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.