환경 변수: 모바일 프로젝트에서의 개념, 사용법 및 설정

저자: IT Sectr 게시일: 2026-05-31 읽는 시간: 8 분

환경 변수는 코드를 변경하지 않고 동작을 구성하기 위해 애플리케이션 시작 시 전달되는 동적 값입니다. 개발, 테스트, 프로덕션 구성을 분리할 수 있습니다. Twelve-Factor App, 2025에 따르면 구성은 코드가 아닌 환경 변수에 저장해야 합니다. 환경 변수는 API 키, 백엔드 URL 및 기능 플래그의 안전한 관리를 보장합니다.

핵심 요점

  • 환경 변수는 다양한 실행 환경에 대해 애플리케이션 구성을 소스 코드에서 분리합니다
  • .env 파일은 KEY=VALUE 형식으로 변수를 저장하며 .gitignore를 통해 리포지토리에서 제외됩니다
  • iOS는 xcconfig 및 Build Settings를 사용하여 컴파일 시간에 변수를 전달합니다
  • Android는 BuildConfig 및 gradle.properties를 사용하여 구성 필드를 생성합니다
  • 보안: 키와 토큰은 CI/CD를 통해 로드해야 하며, 코드나 리포지토리에 저장하지 않아야 합니다

환경 변수란

환경 변수는 운영 체제 API를 통해 애플리케이션 프로세스가 액세스할 수 있는 키-값 쌍입니다. 프로세스가 생성될 때 전달되며 실행 중에만 존재합니다. 소스 코드에 포함된 구성 매개변수와 달리 환경 변수는 값을 변경하기 위해 재컴파일이 필요하지 않습니다. 이것은 Twelve-Factor App의 기본 원칙으로, 코드와 구성 간의 명확한 분리를 보장합니다.

모바일 개발에서 환경 변수는 다양한 환경에 대한 구성 문제를 해결합니다. 개발자는 로컬 서버를, 테스터는 스테이징을, 사용자는 프로덕션을 사용합니다. if-else 조건문과 함께 코드에 세 개의 백엔드 URL을 저장하는 대신, 개발자는 빌드 시간에 환경 변수를 통해 하나의 URL을 전달합니다. 이렇게 하면 코드가 단순화되고 테스트 환경에서 실수로 프로덕션 서버를 사용할 위험이 제거됩니다.

주요 이점은 보안입니다. 민감한 데이터가 코드 리포지토리로 유출되지 않습니다. API 키, Firebase 시크릿, 백엔드 액세스 토큰 및 인증서는 CI/CD를 통해 빌드 환경에 직접 로드됩니다. 공격자가 코드 리포지토리에 액세스하더라도 시크릿을 찾을 수 없습니다. CI 시스템의 보호된 저장소에 저장되며 바이너리 파일 빌드 단계에서만 전달되기 때문입니다.

모바일 개발에서 환경 변수가 필요한 이유

모바일 프로젝트에는 최소한 세 가지 환경(개발, 스테이징, 프로덕션)이 있습니다. 각 환경에는 고유한 구성 세트(서버 URL, 패키지 이름, 서명 체계, 푸시 알림 인증서)가 필요합니다. 환경 변수가 없으면 개발자는 빌드할 때마다 수동으로 구성을 변경해야 하며, 이로 인해 오류가 발생합니다. 테스트 빌드에서 프로덕션 키를 잊어버리면 실제 사용자에게 알림이 전송되거나 유료 API가 소비될 수 있습니다.

환경 분리

환경 변수를 사용하면 코드를 변경하지 않고 백엔드를 전환할 수 있습니다. API_BASE_URL 변수의 값만 변경하면 됩니다. 기능 플래그는 FEATURE_CHAT_ENABLED=true와 같은 변수를 통해 관리되며, 프로덕션에 영향을 주지 않고 스테이징에서 새 기능을 활성화할 수 있습니다. 각 환경에는 빌드 시 로드되는 자체 .env 파일이 있습니다.

dart
class AppConfig {
  static final String apiBaseUrl =
    const String.fromEnvironment('API_BASE_URL',
      defaultValue: 'http://localhost:8080');
}

키 보안

하드코딩된 키는 모바일 애플리케이션의 일반적인 취약점입니다. 공격자는 jadx 또는 Hopper와 같은 도구를 사용하여 APK 또는 IPA를 디컴파일하고 바이너리 파일에서 시크릿을 추출합니다. 난독화조차 문자열 리터럴을 보호하지 못합니다. 디컴파일 후 코드에서 쉽게 찾을 수 있습니다. 환경 변수는 CI/CD를 통해 빌드 시간에 키를 전달하여 이 문제를 해결하며, 로그에서 마스킹됩니다.

kotlin
object Config {
    val apiKey: String =
        System.getenv("API_KEY") ?: throw
            IllegalStateException("API_KEY not set")
}

CI/CD 통합

환경 변수는 빌드 파이프라인과 통합됩니다. GitHub Actions, GitLab CI, Bitrise 및 CircleCI는 로그에 표시되지 않는 비밀 변수를 지원합니다. 빌드 시 CI는 브랜치 또는 태그에 따라 적절한 값을 대체합니다. develop 브랜치에는 스테이징이, v* 태그에는 프로덕션이 사용됩니다. 이는 프로세스를 자동화하고 인적 요소를 제거하여 각 빌드가 올바른 구성 세트를 받도록 보장합니다.

.env 파일 및 관리 라이브러리

.env 파일은 KEY=VALUE 형식으로 환경 변수를 저장하는 표준 방법입니다. 리포지토리에 포함되지 않으며, 대신 모든 변수의 템플릿과 빈 값이 포함된 .env.example이 추가됩니다. 각 개발자는 팀원의 구성에 영향을 주지 않고 로컬 설정으로 자신의 .env 파일을 만듭니다. 다른 환경에는 별도의 파일이 사용됩니다: .env.dev, .env.stage, .env.prod.

bash
# .env.example — 개발자용 템플릿
API_BASE_URL=http://localhost:8080
FEATURE_CHAT_ENABLED=true
SENTRY_DSN=

모바일 프로젝트를 위해 .env 파일 작업을 위한 전문 라이브러리가 있습니다:

  • flutter_dotenv (Flutter) — dotenv.load()를 통해 런타임에 .env에서 변수 로드
  • BuildConfig (Android) — build.gradle 값에서 유형화된 필드 생성
  • xcconfig (iOS) — 다른 Xcode 빌드 스키마에 구성 파일 연결
  • react-native-config (React Native) — .env 파일을 통한 변수 관리

CI/CD의 브랜칭 설정을 통해 다른 .env 파일을 대체할 수 있습니다: 테스트 서버용 .env.dev, 사전 릴리스용 .env.stage, 앱 스토어 게시용 .env.prod. 시크릿이 포함된 파일은 안전한 저장소(Vault, AWS Secrets Manager)에서 로드되며 리포지토리에 저장되지 않습니다. 이는 버전 관리 시스템이 손상되더라도 시크릿이 보호되도록 보장합니다.

iOS 프로젝트의 환경 변수

iOS 생태계는 빌드 수준에서 변수를 관리하기 위해 xcconfig 파일을 사용합니다. 이 파일은 Xcode 스키마에 연결되며 Debug 및 Release 구성 값을 재정의할 수 있습니다. xcconfig 파일은 상속을 지원합니다: 공통 설정이 포함된 기본 파일과 각 환경에 대한 특정 파일을 만들 수 있습니다.

xcconfig 파일 구성

xcconfig 파일은 KEY = VALUE 형식으로 변수를 저장하고 Configuration 설정을 통해 Xcode의 빌드 스키마에 연결됩니다. xcconfig의 변수는 $(VARIABLE_NAME) 구문을 통해 Info.plist에서 사용할 수 있어, 다른 스키마에 다른 번들 식별자와 앱 이름을 사용할 수 있습니다. 빠른 환경 식별을 위해 앱 이름에 Dev 또는 Staging 접미사가 추가됩니다.

bash
# Config/Dev.xcconfig — 개발 구성
API_BASE_URL = http://localhost:3000
BUNDLE_ID_SUFFIX = .dev
APP_DISPLAY_NAME = MyApp Dev

변수를 읽는 Swift 코드

iOS에서 런타임에 변수에 액세스하려면 Configuration.swift 파일을 사용하여 Bundle.main.object(forInfoDictionaryKey:)를 통해 Info.plist에서 값을 읽습니다. 이 접근 방식은 변수가 빌드 시 정의되고 시작 직후 애플리케이션에서 사용할 수 있도록 보장합니다. 값은 모듈 초기화 중에 한 번 읽히고 애플리케이션 수명 주기 동안 빠른 액세스를 위해 캐시됩니다.

swift
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 프로젝트의 환경 변수

Android는 BuildConfig를 통해 환경 변수를 지원합니다. 모듈의 build.gradle 파일에서 필드가 정의되는 자동 생성 클래스입니다. BuildConfig는 각 flavor 및 빌드 유형에 대해 컴파일 시 별도로 생성됩니다. 이렇게 하면 코드에서 조건 연산자를 사용하지 않고 디버그와 릴리스에 대해 다른 값을 가질 수 있어 성능과 보안이 향상됩니다.

BuildConfig 필드 구성

BuildConfig 필드는 defaultConfig 또는 특정 buildTypes에서 buildConfigField를 통해 설정됩니다. 각 환경에 대해 별도의 buildType 또는 productFlavor가 생성됩니다. 이는 엄격한 구성 격리를 보장합니다: 디버그는 로컬 서버를 사용하고 릴리스는 프로덕션을 사용합니다. BuildConfig 필드는 정적으로 유형화되어 코드에서 액세스할 때 오류를 제거합니다.

groovy
// 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.properties 파일은 전역 Gradle 변수를 저장합니다. $variableName 구문을 통해 모든 모듈에서 사용할 수 있으며 종속성 버전, 빌드 플래그 및 API 키를 지정하는 데 사용됩니다. BuildConfig와 달리 gradle.properties는 Gradle 구성 단계에서만 작동하며 애플리케이션 런타임에서는 작동하지 않습니다. 따라서 gradle.properties에 지정된 비밀번호와 API 키는 디컴파일된 코드에 표시되지 않습니다. 컴파일 시 BuildConfig를 생성하는 데만 사용되기 때문입니다.

groovy
# 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는 런타임 액세스를 위한 flutter_dotenv 패키지 또는 플랫폼 변수를 위한 네이티브 채널을 통해 환경 변수를 지원합니다. Dart는 --dart-define을 통해 컴파일 시 값을 전달하는 String.fromEnvironment 생성자도 제공하며, 이는 Flutter 프로젝트에서 선호되는 방법입니다.

BuildConfig와 gradle.properties의 차이점은 무엇인가요?

BuildConfig는 각 buildType 및 flavor에 대해 컴파일 시 생성되는 유형화된 필드를 가진 Java 클래스입니다. gradle.properties는 빌드 구성 단계에서 모든 Gradle 모듈이 액세스할 수 있는 키-값 쌍이 포함된 텍스트 파일입니다. BuildConfig는 애플리케이션 런타임에 작동하고 gradle.properties는 Gradle 스크립트에서만 작동합니다.

.env 파일이 리포지토리로 유출되는 것을 방지하려면 어떻게 해야 하나요?

리포지토리의 .gitignore 파일에 .env를 추가하세요. 리포지토리에는 빈 값과 각 변수에 대한 설명이 포함된 .env.example만 커밋하세요. CI/CD의 경우 GitHub Actions, GitLab CI 또는 Bitrise 설정에서 암호화된 시크릿을 사용하세요. 로그에서 마스킹되며 빌드 완료 후 읽을 수 없습니다.

CI/CD를 통해 환경 변수를 전달하려면 어떻게 해야 하나요?

대부분의 CI 시스템은 비밀 환경 변수를 지원합니다. GitHub Actions에서는 Secrets, GitLab CI에서는 CI/CD Variables, Bitrise에서는 Secrets입니다. 빌드 시 process.env 또는 System.getenv()를 통해 빌드 스크립트로 전달됩니다. 비밀 변수는 빌드 로그에 표시되지 않으며 리포지토리 포크에서 사용할 수 없습니다.

환경 변수를 통한 기능 플래그란 무엇인가요?

기능 플래그는 코드를 재컴파일하지 않고 기능 활성화 또는 비활성화를 제어하는 부울 변수입니다. 예: FEATURE_NEW_PAYMENT=true는 테스트를 위해 스테이징에서 새 결제 시스템을 활성화합니다. 프로덕션에서는 백엔드가 완전히 배포될 때까지 동일한 플래그가 false로 설정됩니다. 이를 통해 변경 사항을 단계적으로 안전하게 롤아웃하고 문제가 발생하면 롤백할 수 있습니다.

요약

  • 환경 변수는 다양한 개발 환경에 대해 구성을 소스 코드에서 분리합니다
  • .env 파일과 .env.example 템플릿은 환경 분리가 있는 팀의 변수 관리 표준입니다
  • iOS xcconfig는 상속 지원 및 Info.plist 통합으로 구성 파일을 Xcode 스키마에 연결합니다
  • Android BuildConfig는 buildType별로 build.gradle에서 유형화된 필드를 개별적으로 생성합니다
  • CI/CD 시크릿은 리포지토리에 저장하지 않고 빌드 시 민감한 데이터를 전달합니다
  • 기능 플래그는 변수를 통해 재컴파일 없이 특정 환경에서 기능을 활성화할 수 있습니다
  • 보안: 키는 CI에서 암호화되어 애플리케이션의 디컴파일 가능한 바이너리 파일로 유출되지 않습니다

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기