- Generated PKCS12 keystore (production-release.jks, alias: upload) - Updated GitHub secrets: KEYSTORE_BASE64/PASSWORD/KEY_ALIAS/KEY_PASSWORD - Added keystores/ to .gitignore - Added distribution/PLAY_CONSOLE_SETUP.md: step-by-step guide to create GCP service account, link to Play Console, first manual upload, and configure PLAY_STORE_SERVICE_ACCOUNT_JSON secret - Installed android-playstore-setup skills (sub-skills) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.5 KiB
GitHub Secrets Setup for Play Store Deployment
Overview
GitHub Actions requires several secrets to deploy your app to the Play Store. This guide explains how to set up each secret.
Prerequisites
- Admin access to GitHub repository
- Service account JSON file from android-playstore-setup
- Production keystore from android-release-build-setup
Required Secrets
Navigate to: Your Repository → Settings → Secrets and variables → Actions → New repository secret
1. SERVICE_ACCOUNT_JSON_PLAINTEXT
What it is: Complete plaintext contents of the Google Cloud service account JSON file (not base64 encoded)
How to get the value:
-
Locate the JSON file downloaded during android-playstore-setup
- Filename:
service-account.json(or similar) - Location: Where you saved it securely
- Filename:
-
Open the file in a text editor
-
Copy the ENTIRE contents
- From the first
{to the last} - Include all whitespace and newlines
- Should be ~2,400 characters
- From the first
-
In GitHub:
- Name:
SERVICE_ACCOUNT_JSON_PLAINTEXT - Value: Paste the copied JSON (plaintext, not base64 encoded)
- Name:
Example format (DO NOT use this, use your actual file):
{
"type": "service_account",
"project_id": "your-gcp-project",
"private_key_id": "abc123...",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----\n",
"client_email": "playstore-deploy@your-project.iam.gserviceaccount.com",
"client_id": "123456789...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
}
Verification:
- ✅ Starts with
{and ends with} - ✅ Contains
"type": "service_account" - ✅ Contains
"client_email"with @...iam.gserviceaccount.com - ✅ Contains
"private_key"section with BEGIN/END markers - ✅ Valid JSON (no syntax errors)
2. SIGNING_KEY_STORE_BASE64
What it is: Your production keystore file encoded as base64
How to get the value:
On Linux/macOS:
base64 -w 0 keystores/production-release.jks
On macOS (alternative):
base64 -i keystores/production-release.jks | tr -d '\n'
On Windows (PowerShell):
[Convert]::ToBase64String([IO.File]::ReadAllBytes('keystores\production-release.jks'))
On Windows (Git Bash):
base64 -w 0 keystores/production-release.jks
Steps:
- Navigate to your project directory
- Run the appropriate command above
- Copy the output (one long string, no line breaks)
- In GitHub:
- Name:
SIGNING_KEY_STORE_BASE64 - Value: Paste the base64 string
- Name:
Verification:
- ✅ String is very long (~5,000+ characters)
- ✅ Contains only alphanumeric characters, +, /, and = (padding)
- ✅ No line breaks or spaces
Common mistakes:
- ❌ Including
-----BEGIN CERTIFICATE-----headers (wrong encoding method) - ❌ Line breaks in the base64 string
- ❌ Encoding the wrong file (debug.keystore instead of production)
3. SIGNING_KEY_ALIAS
What it is: The alias used when creating your production keystore
How to get the value:
From keystores/KEYSTORE_INFO.txt:
Alias: upload
Or list keystore contents:
keytool -list -v -keystore keystores/production-release.jks
# Look for "Alias name:"
Typical values:
upload(recommended by Google Play)releaseproductionkey0(default for some tools)
Steps:
- Check your keystore info file
- In GitHub:
- Name:
SIGNING_KEY_ALIAS - Value: The alias (e.g.,
upload)
- Name:
Verification:
- ✅ Matches the alias in your keystore
- ✅ Case-sensitive (use exact match)
4. SIGNING_STORE_PASSWORD
What it is: Password for the keystore file itself
How to get the value:
From keystores/KEYSTORE_INFO.txt:
Store Password: your-store-password
Steps:
- Get password from secure location (password manager, keystore info file)
- In GitHub:
- Name:
SIGNING_STORE_PASSWORD - Value: The password (case-sensitive)
- Name:
Security notes:
- 🔒 Never commit this password to git
- 🔒 Store in password manager
- 🔒 Use strong password (16+ characters)
5. SIGNING_KEY_PASSWORD
What it is: Password for the specific key within the keystore
How to get the value:
From keystores/KEYSTORE_INFO.txt:
Key Password: your-key-password
Note: Key password and store password are often the same, but can be different.
Steps:
- Get password from secure location
- In GitHub:
- Name:
SIGNING_KEY_PASSWORD - Value: The password (case-sensitive)
- Name:
Verification Checklist
After adding all secrets:
- All 5 secrets are listed in repository settings
- No typos in secret names (they're case-sensitive!)
- SERVICE_ACCOUNT_JSON_PLAINTEXT is valid JSON
- SIGNING_KEY_STORE_BASE64 has no line breaks
- Aliases and passwords match your keystore
- Test deployment workflow to verify
Testing Secrets
To verify secrets are correct without deploying:
- Create a test GitHub Actions workflow:
name: Test Secrets
on: workflow_dispatch
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Test SERVICE_ACCOUNT_JSON_PLAINTEXT
run: |
echo "${{ secrets.SERVICE_ACCOUNT_JSON_PLAINTEXT }}" | jq -r '.client_email'
# Should output: playstore-deploy@your-project.iam.gserviceaccount.com
- name: Test Keystore Decode
run: |
echo "${{ secrets.SIGNING_KEY_STORE_BASE64 }}" | base64 --decode > test.jks
keytool -list -v -keystore test.jks \
-storepass "${{ secrets.SIGNING_STORE_PASSWORD }}" \
-alias "${{ secrets.SIGNING_KEY_ALIAS }}"
# Should show keystore details
- name: Cleanup
if: always()
run: rm -f test.jks
- Run workflow manually
- Check logs for verification
- Delete test workflow after verification
Common Issues
"Invalid service account JSON (SERVICE_ACCOUNT_JSON_PLAINTEXT)"
Symptom: Deployment fails with authentication error
Causes:
- JSON is truncated (didn't copy all of it)
- JSON has syntax errors
- Wrong service account file
Fix:
- Re-copy the entire JSON file
- Validate JSON: https://jsonlint.com/
- Ensure file is from Google Cloud (has "type": "service_account")
"Failed to decode keystore"
Symptom: Build fails when decoding keystore
Causes:
- Base64 string has line breaks
- Wrong file was encoded
- Encoding method was incorrect
Fix:
- Re-encode using commands above
- Ensure output is single line
- Verify keystore file is correct one
"Keystore password incorrect"
Symptom: Signing fails with password error
Causes:
- Typo in password
- Using wrong password (debug instead of release)
- Password has special characters causing shell issues
Fix:
- Verify password from KEYSTORE_INFO.txt
- Test locally first:
keytool -list -keystore production-release.jks - If password has special characters, escape them
"Alias not found"
Symptom: Cannot find key with specified alias
Causes:
- Alias name typo
- Wrong keystore file
- Case sensitivity
Fix:
- List keystore contents:
keytool -list -v -keystore production-release.jks - Copy exact alias name (case-sensitive)
- Update GitHub secret with correct alias
Security Best Practices
1. Least Privilege
- Only grant secret access to necessary workflows
- Use environment-specific secrets if needed
- Regular audit of who has access
2. Rotation
- Rotate service account keys annually
- Update GitHub Secrets after rotation
- Test deployment after rotation
- Document rotation date
3. Monitoring
- Enable GitHub Actions audit log
- Monitor for unauthorized secret access
- Review workflow run history
- Check for failed authentication attempts
4. Backup
- Keep service account JSON in password manager
- Store keystore and passwords in company vault
- Document secret values in secure location (not git!)
- Have disaster recovery plan
5. Separation
- Use different service accounts for dev/staging/prod
- Consider separate repositories for different environments
- Never share production secrets with development
Updating Secrets
When you need to update a secret:
- Navigate to: Repository → Settings → Secrets → Actions
- Find the secret to update
- Click "Update"
- Paste new value
- Click "Update secret"
- Test deployment to verify
Note: Updating a secret does NOT automatically re-run workflows. You need to trigger a new workflow run.
Deleting Secrets
If you need to rotate or remove a secret:
-
Before deleting:
- Ensure new secret is ready (if rotating)
- Update workflows if secret name changes
- Test with new secret
-
Delete:
- Repository → Settings → Secrets → Actions
- Click secret to delete
- Click "Remove secret"
- Confirm deletion
-
After deleting:
- Add new secret immediately
- Test deployment
- Update documentation