skip to content

How would you design EAS development, preview and production build profiles for a fitness app so all three install side by side and reach the right backend?

level: seniorimportance: should knowfreq 24%

answer

  1. one identifier per variant
  2. APP_VARIANT in each profile's env
  3. explicit distribution and environment
  4. a channel per shippable profile
  5. never chain real profiles

basics

~20 s

Give each profile an APP_VARIANT in env so the app config assigns a distinct bundle identifier and package, set distribution and environment explicitly per profile so each loads its own backend variables, and give preview and production separate channels.

solid answer

~50 s

Side by side installs need a **unique iOS bundle identifier and Android package per variant**, so each profile sets `env.APP_VARIANT` and the dynamic app config maps it to `com.example.fitness.dev`, `.preview` or the plain ID, plus a distinct name and icon. Each profile then states its build type explicitly: `development` has `developmentClient: true` and `distribution: "internal"`; `preview` has `"internal"`; `production` keeps the `"store"` default. For the backend, each profile names its EAS environment (`development`, `preview`, `production`), which holds `EXPO_PUBLIC_API_URL` and any secrets; I would not rely on inference, because a dev client without `"internal"` resolves to production. `preview` and `production` each get their own `channel` so over-the-air fixes can target testers separately. All three extend a neutral `base`, never each other, and a `development-simulator` variant adds `ios.simulator: true`. Local `npx expo start` and `eas update` must set the same variant.

code

json · 31 lines
json
{
  "build": {
    "base": {
      "env": { "FEATURE_FLAGS": "remote" }
    },
    "development": {
      "extends": "base",
      "developmentClient": true,
      "distribution": "internal",
      "environment": "development",
      "env": { "APP_VARIANT": "development" }
    },
    "development-simulator": {
      "extends": "development",
      "ios": { "simulator": true }
    },
    "preview": {
      "extends": "base",
      "distribution": "internal",
      "environment": "preview",
      "channel": "preview",
      "env": { "APP_VARIANT": "preview" }
    },
    "production": {
      "extends": "base",
      "environment": "production",
      "channel": "production",
      "env": { "APP_VARIANT": "production" }
    }
  }
}

go deeper

for a junior

Recall that two builds with the same bundle identifier or package replace each other, so each variant needs its own.

for a middle

Explain the moving parts: APP_VARIANT in env, identifiers in app config, explicit distribution and environment, and a channel per shippable profile.

for a senior

Show the failure modes you design out: inferred production environments, inherited channels, secrets in git and a dev server running a different variant.

for a principal

Weigh how many variants the team can afford, since each identifier adds credentials, service registrations and QA surface, against the isolation it buys.

## The requirements A fitness app team wants three builds on the same phone at once: - a **development** build for engineers, with expo-dev-client, talking to a dev backend; - a **preview** build for the coaching team and QA, production-like, talking to staging; - the **production** build from the store, talking to production. Each must be installable without uninstalling the others, must never cross backends, and must receive only the over-the-air updates meant for it. All of that is expressed in `eas.json` build profiles plus the dynamic app config. ## Requirement 1: side by side installs iOS and Android identify an app by its **bundle identifier** and **application ID**. Two builds with the same identifier replace each other, so each variant needs its own. The Expo pattern: 1. Each profile sets a non-secret switch in `env`, for example `APP_VARIANT: "development"`. 2. The dynamic app config reads `process.env.APP_VARIANT` and derives `ios.bundleIdentifier`, `android.package`, the display name and ideally the icon, for example `com.example.fitness.dev`, `com.example.fitness.preview` and `com.example.fitness`. 3. Because `env` is set when EAS CLI evaluates the app config and again on the worker, the identifier is consistent in both places. Consequences to plan for: - Each identifier is a **separate app** to Apple and Google, with its own signing credentials and its own registrations with any service keyed by identifier (push, maps, sign-in providers). - Local development must set the same variable, for example with a `dev` script that runs `APP_VARIANT=development npx expo start`, or the dev server serves a config for the wrong identifier. ## Requirement 2: the right build type per profile | Profile | `developmentClient` | `distribution` | Notes | |---|---|---|---| | `development` | `true` | `"internal"` | ad hoc on iOS, APK on Android | | `development-simulator` | inherited | inherited | extends `development`, adds `ios.simulator: true` | | `preview` | unset | `"internal"` | production-like, installed from a link | | `production` | unset | default `"store"` | submitted to the stores | State `distribution` explicitly on the internal profiles; the default is `"store"`. ## Requirement 3: the right backend - Create EAS environments `development`, `preview` and `production`, each holding `EXPO_PUBLIC_API_URL` and the secrets that environment's builds need. - Set `environment` explicitly on every profile. If it is left out, EAS infers it, and a dev-client profile that forgot `"internal"` resolves to **production**. - Keep secrets out of `env`, which is committed to git, and delete any `env` name that duplicates an EAS variable: the profile's `env` wins silently apart from a warning. ## Requirement 4: updates reach the right audience - Give `preview` and `production` distinct `channel` values; the channel is written into the binary at build time, so each build asks only for its own channel's updates. - Leave `channel` off `development`; development builds are not tied to a channel and can load any compatible update. - When publishing with `eas update`, select the matching EAS environment and variant so the bundle sees the same variables as the binary; the profile's `env` block is not used there. ## Requirement 5: keep the file maintainable - Put tool versions and shared non-secret variables in a neutral `base`. - Have each real profile extend `base` directly. Chaining `preview` onto `production` would inherit the production channel; chaining it onto `development` would inherit the dev client. - Reserve a second level for small variants such as `development-simulator`. ## Rolling it out 1. Convert the static app config to `app.config.ts` and derive identifiers, name and icon from `APP_VARIANT`. 2. Create the three EAS environments and move backend URLs and secrets into them. 3. Rewrite `eas.json` around a neutral `base`, with explicit `distribution`, `environment` and `channel` per profile. 4. Build each profile once, install all three on one device, and confirm from each app's settings or debug screen which backend it reaches. 5. Publish a test update to the `preview` channel and confirm only the preview build receives it. ## Failure modes this design prevents - A tester's preview build overwriting the store app, because both used the same identifier. - A dev client posting workout data to production, because its environment was inferred. - A staging-only update reaching store users, because preview inherited the production channel. - A build that differs between the developer's machine and the worker, because a variable was set in only one place.

  • Why must the fitness app's local dev server also know the APP_VARIANT used by the EAS development profile?
    The development build's identifiers came from `APP_VARIANT=development`. `npx expo start` evaluates the app config itself, so without the same variable the config it serves describes the production variant, with the production name, identifiers and anything else derived from the variant. A package script that sets the variable keeps the dev server and the build consistent.
  • What extra cost does giving each EAS build variant its own bundle identifier create?
    Each identifier is a separate app to Apple and Google: separate signing credentials and provisioning, separate registrations with services keyed by identifier such as push, maps or sign-in providers, and separate store listings if a variant is ever submitted. That cost is why teams usually stop at three variants.
  • Why should the fitness app's preview profile not extend its production profile?
    Everything production sets and preview omits is inherited, including `channel: "production"`. Preview testers would then receive store users' updates, and a staging-only update could not target them. Extending a neutral base and setting each channel explicitly avoids that.

saying these in an interview costs you the question

  • Different profile names are enough for builds to install side by side.
  • The development profile's environment is inferred safely from developmentClient.
  • Preview can extend production because it overrides distribution anyway.
  • Backend secrets belong in each profile's env block for convenience.
  • The dev server needs no APP_VARIANT because only EAS builds read it.