CI/CD Secrets Management#
Triggers#
- When keystores, Firebase service keys, or Fastlane config files are changed
- When adding secrets for a new environment (dev/staging/production)
- When writing steps that use secrets in GitHub Actions workflows
- When errors occur running
make encode_keystore or make github_secrets
Overall Flow#
Local sensitive files â make encode_keystore â .envrc (base64 encoded)
â direnv allow
Loaded as env vars
â
make github_secrets
â gh secret set
GitHub Actions Secrets
â
Decoded in CI workflow
â base64 -d / tar -xzf
Files restored in build environment
Actions#
1. Encoding (Local â .envrc)#
Encode sensitive files to base64 and store them as environment variables in .envrc. The
make encode_keystore target performs this task.
Single File Encoding
# Keystores, JSON files, etc.
base64 -i < file_path >
# Example: base64 -i android/keystore/release.keystore
Store the result in .envrc in export VAR_NAME="<base64>" format.
Directory Encoding (tar.gz â base64)
# fastlane, deploy scripts, etc. directories
tar -czf archive.tar.gz -C < parent_dir > --exclude= " < unnecessary_files > " < target_dir >
base64 -i archive.tar.gz | tr -d ' \n '
rm -f archive.tar.gz
.envrc Update Pattern
If the value exists, replace with sed; otherwise, append with echo >>:
@if grep -q " VAR_NAME " .envrc; then \
sed -i.bak " s|^export VAR_NAME=.*|export VAR_NAME=\ " $$ENCODED\ " | " .envrc; \
else \
echo " export VAR_NAME=\ " $$ENCODED\ " " > > .envrc; \
fi
After encoding, run direnv allow to load environment variables.
2. GitHub Secrets Registration (Env Vars â GitHub)#
make github_secrets registers environment variables loaded from .envrc to GitHub via
gh secret set.
# Register a single secret
gh secret set SECRET_NAME --body " $VALUE " --repo < org > / < repo >
# Register file contents directly (env files, etc.)
gh secret set ENV_STAGING --body " $(cat shared/config/.env.staging) " --repo < org > / < repo >
Helper function pattern:
set_secret() {
name= " $1 " ; val= " $2 "
if [ -n " $val " ]; then
gh secret set " $name " --body " $val " --repo $REPO_PATH
echo " set $name "
else
echo " skip $name (empty) "
fi
}
3. CI Decoding (GitHub Secrets â Build Files)#
Single File: timheuer/base64-to-file Action
- name: Decode Android keystore
uses: timheuer/base64-to-file@v1.2
with:
fileName: release.keystore
fileDir: app/kobic/android/keystore/
encodedString: $ { { secrets.ANDROID_RELEASE_KEY_BASE64 } }
- name: Decode fastlane directory
env:
FASTLANE_ANDROID_BASE64: $ { { secrets.FASTLANE_ANDROID_BASE64 } }
run: |
echo " $FASTLANE_ANDROID_BASE64 " | base64 -d > android/fastlane.tar.gz
mkdir -p android/fastlane
tar -xzf android/fastlane.tar.gz -C android/fastlane --strip-components=1
rm -f android/fastlane.tar.gz
Secret Categories#
Android Keystore (2)#
| Secret Name | Encoding Method | Source File |
ANDROID_RELEASE_KEY_BASE64 |
base64 (file) |
android/keystore/release.keystore |
ANDROID_KEY_PROPERTIES_BASE64 |
base64 (file) |
android/key.properties |
iOS / App Store (2)#
| Secret Name | Encoding Method | Source File |
APPSTORE_CONNECT_API_KEY_BASE64 |
base64 (file) |
ios/fastlane/appstore_connect_api_key.json |
FASTLANE_IOS_BASE64 |
tar.gz -> base64 |
ios/fastlane/ (excluding metadata, screenshots, scripts) |
Firebase App Distribution (3)#
| Secret Name | Encoding Method | Source File |
FIREBASE_DEV_APP_DISTRIBUTION_CREDENTIALS_BASE64 |
base64 |
android/keystore/development.json |
FIREBASE_STG_APP_DISTRIBUTION_CREDENTIALS_BASE64 |
base64 |
android/keystore/staging.json |
FIREBASE_PROD_APP_DISTRIBUTION_CREDENTIALS_BASE64 |
base64 |
android/keystore/production.json |
Firebase Admin SDK (3)#
| Secret Name | Encoding Method | Source File |
FIREBASE_DEV_CREDENTIALS_BASE64 |
base64 |
backend/.../firebase_admin/development.json |
FIREBASE_STG_CREDENTIALS_BASE64 |
base64 |
backend/.../firebase_admin/staging.json |
FIREBASE_PROD_CREDENTIALS_BASE64 |
base64 |
backend/.../firebase_admin/production.json |
Firebase App IDs (~33)#
Combinations of environment (dev/stg/prod) x platform (android, android_console, ios, ios_console, web, macos, macos_console, windows, windows_console, console, widgetbook):
| Pattern | Example |
FIREBASE_{ENV}_{PLATFORM}_ID |
FIREBASE_DEV_ANDROID_ID, FIREBASE_STG_WEB_ID |
Values are set directly in .envrc (not base64 encoded).
Fastlane (2)#
| Secret Name | Encoding Method | Source |
FASTLANE_ANDROID_BASE64 |
tar.gz -> base64 |
android/fastlane/ (excluding metadata, scripts) |
FASTLANE_IOS_BASE64 |
tar.gz -> base64 |
ios/fastlane/ (excluding metadata, screenshots, scripts) |
AWS Deploy (1)#
| Secret Name | Encoding Method | Source |
AWS_DEPLOY_SCRIPTS_BASE64 |
tar.gz -> base64 |
backend/.../deploy/aws/scripts/ |
Serverpod (1)#
| Secret Name | Encoding Method | Source File |
SERVERPOD_PASSWORDS |
base64 |
backend/.../config/passwords.yaml |
Environment Files (2)#
| Secret Name | Encoding Method | Source File |
ENV_STAGING |
direct value (cat) |
shared/config/.env.staging |
ENV_PRODUCTION |
direct value (cat) |
shared/config/.env.production |
Match (iOS Signing) (3)#
| Secret Name | Encoding Method |
MATCH_KEYCHAIN_NAME | direct value |
MATCH_KEYCHAIN_PASSWORD | direct value |
MATCH_GIT_BASIC_AUTHORIZATION_BASE64 | direct value |
Other (1)#
| Secret Name | Encoding Method |
RELEASE_STORE_PASSWORD | direct value |
Output#
- Base64-encoded secret environment variables stored in
.envrc
- Registered in GitHub Actions Secrets
- Decoded in CI workflows to restore build files
Prerequisites#
direnv installed and configured: auto-loads .envrc environment variables
gh (GitHub CLI) installed and authenticated: gh auth login
- Original sensitive files must exist locally (included in
.gitignore)
Adding New Secrets#
- Add encoding logic to the
encode_keystore target in Makefile
- Add
set_secret call to the github_secrets target in Makefile
- Add decoding step to CI workflow
- Verify original file path is included in
.gitignore
Notes#
.envrc must be included in .gitignore and never committed
- After running
direnv allow, environment variables are loaded into the shell
gh secret set overwrites existing values (upsert behavior)
- Firebase App IDs are public values but managed as secrets for consistency
-
When encoding with tar.gz, exclude unnecessary large files like metadata/screenshots with
--exclude