Mobile testing with Expo
The hard rule first: iOS Simulator and the Android emulator cannot run on a
Linux host. The Simulator needs macOS + Xcode; the emulator needs host
hardware acceleration. So no container or your host outpost runs them - ever. What CAN
run remotely is the part you actually want isolated: npm install, the Metro
bundler, builds, tests, and the agents.
So the split for an Expo project is:
| Runs in the outpost (your host) | Runs on your Mac |
|---|---|
npm/pnpm install, dependency code |
Xcode, iOS Simulator |
Metro / expo start bundler |
Android emulator |
| your YOLO agents | a physical phone over USB |
eas build / eas submit (just the CLI; build runs in EAS cloud) |
what you LOOK at |
Option 1 (recommended): physical phone + tunnel to the outpost
Section titled “Option 1 (recommended): physical phone + tunnel to the outpost”Your real device connects to the Metro bundler running in the container, over a public tunnel, from anywhere. No Mac build toolchain needed for JS-only changes.
In the project container (outpost sh <project>):
npm i -g @expo/ngrok # one-time: --tunnel needs this (else it hangs/prompts)npx expo start --tunnel # a dev server reachable from your phone over the internet--tunnel uses ngrok to expose the bundler at a public *.exp.direct URL, so
Expo Go (or your dev client) on the phone reaches it without being on the same
network. Without @expo/ngrok installed, the command errors and tries to prompt -
so install it first (or bake it into the base image). Scan the QR code in Expo Go
(or your custom dev client) and the app loads, with fast refresh.
Note that the tunnel exposes your bundler on a public URL while it runs; stop it when you are done.
If you prefer your own tunnel, the base image ships cloudflared: run the dev
server normally and cloudflared tunnel --url http://localhost:<port> for an
https://<random>.trycloudflare.com URL.
This covers the everyday loop: edit JS in the outpost, agents work in the outpost, see it live on your phone. Latency is a little higher than LAN but fine for iteration.
Option 2: native builds without a Mac - EAS Build (cloud)
Section titled “Option 2: native builds without a Mac - EAS Build (cloud)”When you need a real signed iOS/Android binary (native module changes, store builds, a custom dev client), use EAS Build. It compiles in Expo’s cloud on macOS/Linux build workers, so you do not need local Xcode/Android Studio. From the outpost:
npm i -g eas-cli # (or bake into the base / install per project)eas login # alias of eas account:loginnpx expo install expo-dev-client # required before a development buildeas build --platform android # or ios, or alleas build --platform ios --profile development # a custom dev client- iOS builds require Apple credentials; EAS can manage signing for you or use credentials you provide. A real-device iOS build still needs an Apple Developer account.
- A development build (
--profile development) gives you a custom dev client you install on the phone, thennpx expo start --dev-client --tunnelfrom the outpost drives it - same remote loop as Option 1 (so the same@expo/ngrokprerequisite applies). It is for apps with native modules Expo Go does not include. Installexpo-dev-clientin the project first (shown above). - For an iOS Simulator dev build specifically, set
ios.simulator: trueon thedevelopmentprofile ineas.json(otherwise you get a device build that needs a registered device). You still run that simulator build on the Mac (Option 3), not on the outpost. eas submitships a finished build to TestFlight / Play Console from the CLI.
Option 3: the Mac for the tight simulator loop
Section titled “Option 3: the Mac for the tight simulator loop”When you specifically want the iOS Simulator (fastest visual loop, no phone), that part is a Mac-local activity. Two clean ways:
- Keep a LOCAL copy of the same project (same approach, local Docker/OrbStack) for simulator work, and let the outpost handle agents/long tasks. The setup is identical, so this is just “run the container locally” when you need the simulator.
- Or run only Metro locally on the Mac and the Simulator against it, accepting that local Metro runs your JS deps on the Mac. Prefer the first option to keep dependency execution boxed.
Practical recommendation
Section titled “Practical recommendation”- Day to day: Option 1 (phone +
--tunnelfrom the outpost). Most Expo work is JS, and this keeps install/build/agents isolated on your host. - Native binaries / store builds: Option 2 (EAS Build), no Mac toolchain needed.
- iOS Simulator when you want it: Option 3, on the Mac, using your local copy.
The one thing to internalize: the outpost bundles and builds; the Mac (or your phone) is what you look at.