RORK LABJP
APPLE — Apple's event is September 9, with the iOS 27 release candidate landing the same day. General release is September 14, preorders September 12, and retail September 18TESTING — That leaves five working days between the RC and the public release. For React Native and Expo apps, it is the last clean window to run everything against Xcode 27 and the iOS 27 SDKSIRI — iOS 27 rebuilds Siri from the ground up, which puts anything using Siri Intents or voice input at the top of the list to verifyPLAY — Google Play's target API level 36 requirement took effect on August 31. If you did not make it, an extension request through November 1 is still available in Play ConsoleEXPO — expo@57.0.17 moves React Native to 0.86.3 and clears the Hermes V1 memory regression that hit apps importing react-native-worklets or reanimatedRORK — The two Rork products build different things: the original generates React Native via Expo, while Rork Max generates Swift and compiles it on a cloud Mac fleetAPPLE — Apple's event is September 9, with the iOS 27 release candidate landing the same day. General release is September 14, preorders September 12, and retail September 18TESTING — That leaves five working days between the RC and the public release. For React Native and Expo apps, it is the last clean window to run everything against Xcode 27 and the iOS 27 SDKSIRI — iOS 27 rebuilds Siri from the ground up, which puts anything using Siri Intents or voice input at the top of the list to verifyPLAY — Google Play's target API level 36 requirement took effect on August 31. If you did not make it, an extension request through November 1 is still available in Play ConsoleEXPO — expo@57.0.17 moves React Native to 0.86.3 and clears the Hermes V1 memory regression that hit apps importing react-native-worklets or reanimatedRORK — The two Rork products build different things: the original generates React Native via Expo, while Rork Max generates Swift and compiles it on a cloud Mac fleet
Articles/App Dev
App Dev/2026-09-02Intermediate

Leave image out of eas.json and an SDK bump moves your Xcode and pnpm too

If eas.json has no image field, your builds run on the auto alias. Move one SDK alias and Xcode, Node.js, pnpm and fastlane all change together. Here is how to read the image name out of your last green build and decide whether to pin it before Xcode 27 lands.

EAS Build17eas.jsonExpo196Xcode3build environment

When a build fails, the first place you look is whatever you touched. I did that for years.

But once you are running a few apps side by side, a day arrives when nothing on your side changed and the result changed anyway. Same commit. Same dependencies. What moved was the Mac on the other end of the pipe.

Scroll an EAS Build log all the way back to the top and there is a quiet section called Spin up build environment. It names the image that build actually ran on. For a long stretch I had never read that line once.

The default when you leave it out is not latest

This is the part I see misread most often.

If you do not put an image field in eas.json, EAS Build runs the job on the auto alias. Not latest. The auto alias picks an image based on your project configuration, your Expo SDK version, and your React Native version.

So the default behaviour is not "always newest." It is "chosen to suit your SDK." That sounds calm, and mostly it is. The flip side is that the image changes the moment you bump your SDK.

The value you can set is either a full image name or one of the aliases: auto, latest, or a per-SDK alias such as sdk-57. They behave differently from one another.

What you setWhen its contents moveWhen it fits
Full name (e.g. macos-tahoe-26.5-xcode-26.6)Almost never; minor updates onlyYou want a frozen environment
auto (default)When you raise the SDK or RN versionYou want to follow Expo's judgement
Per-SDK, e.g. sdk-57With every new SDK releaseYou track environments by SDK
latestWith every new image releaseYou want the newest Xcode early

latest does not mean "stable." It means "whatever image is newest gets this label." Read it as the stable option and your reasoning ends up pointing the wrong way.

What actually swaps when one SDK alias moves

That stays abstract until you put two images next to each other, so here are the iOS images for SDK 56 and SDK 57 from Expo's published build server list.

Itemsdk-56 (macos-tahoe-26.4-xcode-26.4)sdk-57 (macos-tahoe-26.5-xcode-26.6, latest)
Xcode26.4 (17E202)26.6 (17F113)
macOSTahoe 26.4.1Tahoe 26.5.2
Node.js22.22.222.23.1
pnpm10.33.311.9.0
fastlane2.233.12.236.1
Maestro2.5.12.6.1

The line that stops me is pnpm: 10.33.3 to 11.9.0, straight across a major version. One alias step and your package manager changes generation. Android does the same thing. Going from ubuntu-26.04-jdk-17-ndk-r27b (sdk-56) to ubuntu-26.04-jdk-17-ndk-r27b-sdk-57 takes pnpm from 10.33.3 to 11.9.0 and node-gyp from 12.3.0 to 13.0.0.

A node-gyp major moving under a project with native modules is quiet, but it is not small. All of that rides underneath a one-line changelog entry that says "bumped Expo SDK."

These figures are what Expo's build server infrastructure page listed at the time of writing. Images get added regularly, so open that page yourself before you make a call on it.

It is worth being precise about which of these you can actually control elsewhere. Node.js you can usually influence through your project setup. Xcode, the macOS version, and the Ruby that fastlane runs on are decided by the image and nothing else. That is the practical reason the image field exists: it covers the pieces there is no other knob for.

Pull the image name out of your last green build

Before deciding whether to pin, you need to know where you already are. The order I use:

  1. Open your most recent successful build in the EAS dashboard
  2. Scroll the log to the Spin up build environment section
  3. Copy the image name printed there

If you are running on auto, that name is the environment your app is genuinely shipping from today. It is not written in eas.json, so the log is the only place it exists.

Once you have the name, it drops straight into your config.

{
  "build": {
    "production": {
      "ios": {
        "image": "macos-tahoe-26.5-xcode-26.6"
      },
      "android": {
        "image": "ubuntu-26.04-jdk-17-ndk-r27b-sdk-57"
      }
    }
  }
}

You can also split it per profile. I prefer pinning production while letting a preview profile roll forward, so something in my setup meets the new environment early. Keeping one place that is allowed to break is what lets a pinned production build avoid quietly falling behind.

{
  "build": {
    "preview": {
      "distribution": "internal",
      "ios": { "image": "latest" }
    },
    "production": {
      "ios": { "image": "macos-tahoe-26.5-xcode-26.6" }
    }
  }
}

When pinning helps, and when it stops helping

Pinning is not a free win. Both directions carry a cost.

What you buy is reproducibility. Rebuild the same commit next month and it goes through the same Xcode and the same fastlane. That removes a specific kind of bad afternoon: a build failing right before submission with no obvious cause.

What you owe is attention. Expo aims to support the stable Xcode releases that App Store Connect will still accept, which in practice means the newest one and the one before it, until Apple raises its minimum Xcode requirement again. When that requirement moves, a pinned image eventually stops being submittable. Pinning is not "you never have to decide." It is "you choose when to decide."

So if you pin, put the date and the reason in the commit message. I have skipped that step more than once and then spent an evening working out why a particular image name was sitting there.

There is a middle position that suits a lot of solo projects. Pin nothing, but read the image name after every successful production build and keep the last few in a note. You give up reproducibility, and in exchange you always know what changed and when, which is most of what pinning was protecting you from. If your release cadence is slow enough that months pass between builds, I would pin. If you ship every couple of weeks, the note may be enough.

What I would avoid is the third state: no pin, no note, and no habit of reading the log. That is where a failing build turns into an afternoon of bisecting your own code for a change that was never yours.

Counting what your local toolchain actually has, rather than what you assume it has, comes up from another angle in checking which expo version your app is really on.

One thing worth deciding before Xcode 27

iOS 27 is reported to ship on September 14. As new Xcode releases follow, both latest and the per-SDK aliases will point somewhere else than they do today.

One task is enough for now. Open the log of your last successful build and copy the image name from Spin up build environment. Just copy it. Whether to pin is a decision you can make afterwards, with the name in front of you.

If you are about to start device checks, pairing this with the steps in touching your published app on a spare iPhone before the release makes the sequencing easier. And if the Android deadline is running alongside, confirming your effective targetSdkVersion belongs in the same week.

Thank you for reading. Build environments are the part nobody looks at while things are working. Opening it once during a calm week tends to pay for itself during a loud one.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-08-30
After Bumping targetSdk to 36, Which Part of Your Dependencies Should You Actually Read
I opened six real dependency AARs and counted what they contribute to my manifest. Not one declares targetSdkVersion, and twelve permissions arrive that I never wrote. Here is what to check before you submit.
App Dev2026-08-26
What Android 16 ignores on tablets, and why app.json gives you no way out
Targeting API level 36 means screenOrientation is ignored on displays at least 600dp wide. Here is how to check your project without a tablet, why app.json cannot express a fix, and what the opt-out property actually restores.
App Dev2026-08-25
Put Your Live Rork App on a Spare iPhone Before iOS 27 Ships
iOS 27 arrives in September and developer beta 7 is already out. Here is how to turn an old iPhone into a beta device, what to check first, and which fixes you can ship from JavaScript without waiting for review.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →