Section 03

First-time setup

Do these in order — each step depends on the one before it. Budget about ninety minutes the first time, most of it spent in Apple and Google's web consoles rather than the terminal.

1

Install leas into your app

From your Expo or React Native project directory:

npm install --save-dev leas-cli
npx leas init

leas init writes a leas.json. Its shape deliberately mirrors eas.json — profiles, extends, and per-platform blocks all behave the same — so an existing Expo project migrates with little more than a rename.

2

Create a folder for credentials — and never commit it

mkdir -p credentials/ios credentials/android
grep -q '^credentials/' .gitignore || echo 'credentials/' >> .gitignore
Do this before you download anything

A distribution certificate committed to a repo is a certificate you have to revoke. Set the ignore rule first, download second.

3

Apple — create your distribution certificate

This is the identity that says "this build genuinely came from our team." Generate the key locally so the private half never leaves your machine:

openssl req -new -newkey rsa:2048 -nodes \
  -keyout credentials/ios/dist.key \
  -out credentials/ios/dist.csr \
  -subj "/emailAddress=you@yourcompany.com/CN=Your Company/C=US"

Go to developer.apple.com → Certificates, IDs & Profiles → Certificates → +, choose Apple Distribution, upload dist.csr, and download the resulting .cer. Then combine the two halves into the .p12 leas uses:

openssl x509 -in ~/Downloads/distribution.cer -inform DER \
  -out credentials/ios/dist.pem -outform PEM

openssl pkcs12 -export \
  -inkey credentials/ios/dist.key \
  -in credentials/ios/dist.pem \
  -out credentials/ios/dist.p12 \
  -passout pass:CHOOSE_A_PASSWORD
Apple

A team can hold only a small number of distribution certificates at once — historically two. If the button is greyed out, someone on your team already used the slots. Find out who before you revoke anything: revoking a certificate instantly invalidates every provisioning profile built on it, which breaks everyone else's builds.

4

Apple — register the app and create a provisioning profile

Still in Certificates, IDs & Profiles:

  • Identifiers → + — register your bundle ID (for example com.yourcompany.app). It must match ios.bundleIdentifier in your app.json exactly.
  • Profiles → + → App Store Connect — pick that identifier, pick the distribution certificate from step 3, name it something you will recognise in a year, and download the .mobileprovision into credentials/ios/.

A provisioning profile is just the notarised statement "this certificate may ship this app." That is why it dies whenever either half changes.

5

Apple — create the App Store Connect API key

This is what leas uses to upload. Go to App Store Connect → Users and Access → Integrations → App Store Connect API, choose Team Keys, and generate a key with the App Manager role.

Record three things:

  • The Key ID — a short code like ABCD1234EF.
  • The Issuer ID — a UUID shown above the key list, shared by all your keys.
  • The .p8 file itself.
You get exactly one download

Apple lets you download the .p8 once and never again. Save it to credentials/ios/ and put a copy in your password manager in the same minute. If you lose it, the only path forward is revoking the key and issuing a new one.

6

Android — create your upload keystore

keytool -genkeypair -v \
  -keystore credentials/android/upload.keystore \
  -alias upload -keyalg RSA -keysize 2048 -validity 10000 \
  -dname "CN=Your Company, O=Your Company, C=US"

You will be prompted for a password. Use the same one for the store and the key unless you have a reason not to — it keeps the configuration simpler.

Android

With Play App Signing enabled (the default for new apps), Google holds the real app signing key and you hold this upload key. That split is a gift: if you ever lose the upload key, you request a reset and carry on. Losing the app signing key in the old world meant you could never update your app again.

7

Android — create the Play service account and grant it access

Two halves, in two different consoles. Missing the second half is the single most common setup mistake.

In Google CloudIAM & Admin → Service Accounts → Create. Then open it, go to Keys → Add key → Create new key → JSON, and save the download to credentials/android/play-service-account.json. Note the account's email, which looks like leas-publisher@your-project.iam.gserviceaccount.com.

In Play ConsoleUsers and permissions → Invite new users. Paste that service account email, grant it access to your app, and give it the release permissions (at minimum: view app information and manage releases for the tracks you intend to publish to).

Why this trips everyone

Creating the service account in Google Cloud gives it an identity but no authority. If leas reports 403 · The caller does not have permission, you have almost certainly done the first half and not the second.

8

Fill in leas.json

Point the config at what you just created. Copy leas.example.json as your starting point and edit the identifiers:

"submit": {
  "production": {
    "ios": {
      "ascAppId": "1234567890",            // from the App Store Connect URL
      "ascApiKeyId": "ABCD1234EF",
      "ascApiIssuerId": "69a6de70-...",
      "ascApiKeyPath": "credentials/ios/AuthKey_ABCD1234EF.p8",
      "waitForProcessing": true
    },
    "android": {
      "packageName": "com.yourcompany.app",
      "serviceAccountKeyPath": "credentials/android/play-service-account.json",
      "track": "internal"
    }
  }
}
9

Build locally first

Always prove it works on your own machine before involving the cloud. Local failures give you the full error; cloud failures give you a log you have to go read.

export LEAS_IOS_DIST_CERT_PASSWORD='the password from step 3'
export LEAS_ANDROID_KEYSTORE_PASSWORD='the password from step 6'
export LEAS_ANDROID_KEY_ALIAS='upload'

npx leas build --platform android --profile production
npx leas build --platform ios --profile production

Artifacts land in .leas/build/. When both succeed, submit one by hand to confirm the upload credentials work end to end:

npx leas submit --platform android --profile production
10

Upload your secrets and turn on the cloud

Same credentials, different delivery. Everything moves as base64 so it survives being an environment variable:

base64 -i credentials/ios/dist.p12          | gh secret set LEAS_IOS_DIST_CERT_BASE64
base64 -i credentials/ios/profile.mobileprovision | gh secret set LEAS_IOS_PROVISIONING_PROFILE_BASE64
base64 -i credentials/ios/AuthKey_ABCD1234EF.p8   | gh secret set LEAS_ASC_API_KEY_BASE64
base64 -i credentials/android/upload.keystore     | gh secret set LEAS_ANDROID_KEYSTORE_BASE64
base64 -i credentials/android/play-service-account.json | gh secret set LEAS_PLAY_SERVICE_ACCOUNT_BASE64

gh secret set LEAS_IOS_DIST_CERT_PASSWORD
gh secret set LEAS_ANDROID_KEYSTORE_PASSWORD
gh secret set LEAS_ANDROID_KEY_ALIAS
gh secret set LEAS_ANDROID_KEY_PASSWORD
gh secret set LEAS_ASC_KEY_ID
gh secret set LEAS_ASC_ISSUER_ID

Then ship the whole thing from one command:

npx leas build --platform all --profile production --auto-submit --cloud