이 글에서 다루는 것

Play Store에 앱을 배포하려면 반드시 릴리즈 서명(signing)이 필요합니다. keystore.properties를 활용해 build.gradle에서 빌드할 때 자동으로 서명되도록 설정하는 방법을 정리합니다.
민감한 키 정보를 코드 바깥의 별도 파일로 분리하고, .gitignore로 Git에서 제외한 뒤, build.gradle에서 해당 파일을 읽어 서명 설정에 적용하는 방식입니다.

전체 구조

keystore.properties과 keystore 파일 새로 추가합니다.

your_project/
├── android/
│   ├── keystore.properties      ← 키 정보 (Git 제외)
│   └── app/
│       └── build.gradle
└── my-release-key.jks           ← keystore 파일 (Git 제외)

1단계: keystore 파일 생성

아직 keystore가 없다면 아래 명령어로 생성합니다.

keytool -genkey -v -keystore my-release-key.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias my-key-alias

생성된 .jks 파일은 프로젝트 루트 또는 android/ 디렉토리 안에 보관합니다.

2단계: keystore.properties 생성

프로젝트의 android/ 폴더 안에 keystore.properties 파일을 만들고 키 정보를 작성합니다.

storeFile=../../my-release-key.jks
storePassword=your_store_password
keyAlias=my-key-alias
keyPassword=your_key_password

storeFile 경로는 android/app/build.gradle 기준 상대경로입니다. Flutter 프로젝트에서 keystore를 루트에 두었다면 ../../로 설정합니다.

3단계: .gitignore에 추가

서명 관련 파일은 절대 Git에 올라가서는 안 됩니다.

# Android Signing
keystore.properties
*.jks
*.keystore

4단계: build.gradle 설정 (Groovy DSL)

android/app/build.gradle 상단에 properties 파일을 읽는 코드를 추가하고, signingConfigsbuildTypes에 적용합니다.

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()

// if (exists()) 없이 작성 → 파일이 없으면 즉시 빌드 에러 발생
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
    signingConfigs {
        release {
            storeFile     file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
            keyAlias      keystoreProperties['keyAlias']
            keyPassword   keystoreProperties['keyPassword']
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

주의: if (keystorePropertiesFile.exists()) 가드를 추가하면 파일을 못 찾을 때 조용히 넘어가 debug 키로 서명됩니다. 실수를 즉시 알 수 있도록 가드 없이 작성하는 것을 권장합니다.

4단계 (대안): build.gradle.kts 설정 (Kotlin DSL)

최근 생성되는 프로젝트는 Kotlin DSL을 기본으로 사용합니다.

import java.util.Properties
import java.io.FileInputStream

val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties().apply {
    load(FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        create("release") {
            storeFile     = file(keystoreProperties["storeFile"] as String)
            storePassword = keystoreProperties["storePassword"] as String
            keyAlias      = keystoreProperties["keyAlias"] as String
            keyPassword   = keystoreProperties["keyPassword"] as String
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

5단계: 빌드 및 서명 확인

# Flutter 프로젝트
flutter build appbundle --release
flutter build apk --release

# 일반 Android 프로젝트
./gradlew bundleRelease
./gradlew assembleRelease

빌드 후 서명 정보를 확인해 CN=Android Debug가 아닌 본인 키 정보가 표시되는지 검증합니다.

keytool -printcert -jarfile build/app/outputs/flutter-apk/app-release.apk

서명이 정상 적용됐다면 잘못된 비밀번호 입력 시 아래와 같은 에러가 발생해야 합니다.

FAILURE: Build failed with an exception.
Execution failed for task ':app:signReleaseBundle'.
> Failed to read key my-key-alias from store "...jks": Cannot recover key

이 에러가 나오지 않고 빌드가 성공한다면 signingConfig이 실제로 적용되지 않은 상태이므로 경로 설정을 다시 확인해야 합니다.

CI/CD 환경 설정 (GitHub Actions)

로컬에서는 keystore.properties를 사용하고, CI 환경에서는 Secrets로 대체합니다.

GitHub Secrets 등록 항목

Secret 이름
KEYSTORE_BASE64 base64 my-release-key.jks로 인코딩한 문자열
KEY_STORE_PASSWORD storePassword
KEY_ALIAS keyAlias
KEY_PASSWORD keyPassword

workflow .yml 예시

- name: Decode keystore
  run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > my-release-key.jks

- name: Create keystore.properties
  run: |
    echo "storeFile=../../my-release-key.jks" > android/keystore.properties
    echo "storePassword=${{ secrets.KEY_STORE_PASSWORD }}" >> android/keystore.properties
    echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/keystore.properties
    echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/keystore.properties

- name: Build release AAB
  run: flutter build appbundle --release

정리

keystore.properties 방식의 핵심은 민감 정보를 코드에서 완전히 분리하는 것입니다. 로컬에서는 properties 파일로, CI/CD에서는 환경 Secrets로 동일한 build.gradle을 재사용할 수 있어 관리가 단순해집니다.

서명이 올바르게 적용되었는지는 반드시 keytool -printcert로 확인하는 습관을 들이는 것을 추천합니다.