A complete guide to iOS development using the Devbox iOS plugin. This guide covers setup, device management, development workflow, testing, and troubleshooting.
The iOS plugin enables reproducible iOS development by automatically discovering Xcode and managing iOS simulators project-locally. It provides:
- Project-local simulator management: Device definitions stored in your devbox.d directory, isolated from system-wide simulator configuration
- Automatic Xcode discovery: Multi-strategy detection with caching for fast shell initialization
- Device management: JSON-based device definitions with CLI commands for creating, updating, and managing simulators
- Simulator control: Scripts for starting and stopping simulators
- App deployment:
ios.sh runauto-detects your.appbundle, extracts the bundle ID, and deploys to the simulator
The plugin auto-detects your Xcode project, .app path, and bundle ID at runtime. You define a build:ios script in your devbox.json to handle the actual Xcode build, then ios.sh run handles everything else. See Adding Build Scripts for patterns.
Pure shells with devbox run --pure create test-specific simulators and clean up after execution, ensuring isolated, reproducible testing.
Devbox handles downloading other tools — you don't need to install them separately.
Create or modify your devbox.json to include the iOS plugin:
{
"include": ["github:segment-integrations/devbox-plugins?dir=plugins/ios"]
}Build output is stored in .devbox/virtenv/ios/DerivedData by default (configurable via IOS_DERIVED_DATA_PATH).
Initialize the Devbox environment:
devbox shellThis command:
- Discovers Xcode installation automatically
- Creates device definitions in your devbox.d directory
- Sets up runtime scripts
- Configures environment variables for iOS development
- Caches Xcode path for fast subsequent initialization
Verify the installation:
devbox run ios.sh infoThis displays Xcode developer directory, iOS SDK version, available runtimes, and device configuration.
You can also run diagnostics to check your setup:
devbox run doctorThis checks for Xcode installation, command-line tools, xcrun and simctl availability, device definitions, and lock file status.
Device definitions are JSON files that describe simulator configurations. Each plugin install creates a devices/ directory inside your devbox.d/ folder with default device files.
The plugin includes two default devices:
min.json- Minimum supported iOS version (iOS 15.4, namediPhone 13)max.json- Latest iOS version (iOS 26.2, namediPhone 17)
These files live in your devbox.d/ directory, which is the devbox plugin configuration folder. The plugin creates a subdirectory there with a devices/ folder containing them (e.g., devbox.d/<plugin-dir>/devices/min.json). The filenames (min, max) are short nicknames you use in commands. The name field inside each JSON file is the simulator display name that appears in device listings.
View all available device definitions:
devbox run ios.sh devices listOutput shows device names and iOS runtime versions.
To see what device types and runtimes are available on your system:
# List available device types (e.g., iPhone 15, iPhone 13, iPad Pro)
xcrun simctl list devicetypes
# List available iOS runtimes (iOS versions installed)
xcrun simctl list runtimesCreate a device with specific configuration:
devbox run ios.sh devices create iphone15 --runtime 17.5Parameters:
- Device name (first argument): Used as filename and identifier
--runtime(required): iOS version (e.g., "17.5", "18.0", "15.4")
The device name typically corresponds to iOS device types like:
iPhone 15 ProiPhone 14iPad ProiPad Air
Show configuration for a specific device:
devbox run ios.sh devices show iphone15Modify an existing device definition:
# Update iOS runtime version
devbox run ios.sh devices update iphone15 --runtime 18.0
# Rename device
devbox run ios.sh devices update iphone15 --name iphone15proRemove a device definition:
devbox run ios.sh devices delete iphone15By default, the plugin evaluates all devices. To optimize evaluation time (especially in CI), select specific devices in devbox.json:
{
"env": {
"IOS_DEVICES": "min,max"
}
}To evaluate all devices, use an empty string:
{
"env": {
"IOS_DEVICES": ""
}
}After creating, updating, or deleting devices, regenerate the lock file:
devbox run ios.sh devices evalThe lock file (in your devices directory) tracks which devices should be created and includes checksums for validation. Commit this file to version control.
Ensure local simulators match device definitions:
devbox run ios.sh devices syncThis creates or updates simulators to match your JSON device definitions. The sync process reports:
- Matched: Simulators that already exist and match definitions
- Recreated: Simulators that needed to be deleted and recreated
- Created: New simulators created
- Skipped: Devices skipped due to missing runtimes
Run this after modifying device files or pulling changes.
Start an iOS simulator for testing:
# Start default device
devbox run start:sim
# Start specific device by nickname
devbox run start:sim iphone15The simulator boots if not already running. The default device is configured via IOS_DEFAULT_DEVICE (defaults to max).
Set the default device in devbox.json:
{
"env": {
"IOS_DEFAULT_DEVICE": "max"
}
}Stop all running simulators:
devbox run stop:simThe plugin provides simulator and device management. Build and deploy commands are specific to your Xcode project, so you define them in your devbox.json. Here's a typical setup:
{
"include": ["github:segment-integrations/devbox-plugins?dir=plugins/ios"],
"shell": {
"scripts": {
"build:ios": [
"ios.sh xcodebuild -scheme MyApp -configuration Debug -destination 'generic/platform=iOS Simulator' build"
],
"build:release": [
"ios.sh xcodebuild -scheme MyApp -configuration Release build"
],
"start:app": [
"ios.sh run ${1:-}"
]
}
}
}The ios.sh run command handles the full deployment pipeline: starts the simulator, runs your build:ios script, auto-detects the .app bundle, extracts the bundle ID from Info.plist, installs, and launches. The ${1:-} syntax passes an optional device nickname through.
How app auto-detection works: After building, ios.sh run finds your .app bundle using this precedence chain:
IOS_APP_ARTIFACTenv var — if set, resolves the path/glob relative to project rootxcodebuild -showBuildSettings— queries your Xcode project for BUILT_PRODUCTS_DIR + FULL_PRODUCT_NAME (auto-detected from project)- Recursive search of the project directory for
.appbundles, skippingPods/,.build/,node_modules/,.devbox/, and similar directories - Recursive search of the current working directory (if different from project root)
In most projects, step 2 or 3 finds the right .app automatically with no configuration needed. If auto-detection doesn't work (e.g., multiple .app bundles, non-standard project layout), set IOS_APP_ARTIFACT explicitly:
{
"env": {
"IOS_APP_ARTIFACT": "DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app"
}
}With the scripts defined above, you can:
# Build the app
devbox run build:ios
# Start simulator, install, and launch
devbox run start:app
# Run on a specific device
devbox run start:app minSee the iOS example project for a complete working setup. The example project uses a local plugin path for development. If you use it as a template, change the include to the GitHub URL shown above.
Typical development session:
# 1. Enter devbox shell
devbox shell
# 2. Build and deploy (starts simulator, builds, installs, and launches)
devbox run start:app
# 3. Make code changes, rebuild, and redeploy
devbox run start:app
# 5. Stop simulator when done
devbox run stop:simThe iOS example project includes E2E test infrastructure using process-compose. You can use it as a template for your own project.
Copy the example test suite:
cp -r examples/ios/tests/ your-project/tests/Add a test script to your devbox.json:
{
"shell": {
"scripts": {
"test:e2e": [
"process-compose -f tests/test-suite.yaml --no-server --tui=${TEST_TUI:-false}"
]
}
}
}The test suite automatically adjusts behavior based on the execution mode:
Normal Mode (Developer):
- Reuses existing simulators
- Keeps app and simulator running after test
- Fast iteration for development
devbox run test:e2ePure Mode (CI):
- Creates fresh test-specific simulator (with " Test" suffix)
- Stops and cleans up everything after test
- Reproducible, isolated environment
devbox run --pure test:e2eThe DEVBOX_PURE_SHELL environment variable is automatically set by devbox when using the --pure flag. Scripts auto-detect this to determine whether to create fresh, isolated emulators/simulators.
Run tests with TUI for real-time monitoring:
TEST_TUI=true devbox run test:e2eThe TUI shows process status, logs, and resource usage during test execution.
Configure for your app in devbox.json:
{
"include": ["github:segment-integrations/devbox-plugins?dir=plugins/ios"],
"shell": {
"scripts": {
"test:e2e": [
"process-compose -f tests/test-suite.yaml --no-server --tui=${TEST_TUI:-false}"
]
}
}
}Create device definitions:
mkdir -p devbox.d/ios/devices
# Create min device (oldest supported iOS)
devbox run ios.sh devices create min --runtime 15.4
# Create max device (latest iOS)
devbox run ios.sh devices create max --runtime 26.2
# Generate lock file
devbox run ios.sh devices evalCustomize test behavior with environment variables:
# Set simulator boot timeout (seconds)
BOOT_TIMEOUT=120 devbox run test:e2e
# Run headless (no simulator GUI - always headless on CI)
SIM_HEADLESS=1 devbox run test:e2e
# Enable debug logging
IOS_DEBUG=1 devbox run test:e2eEnvironment variables:
| Variable | Description | Default |
|---|---|---|
IOS_APP_ARTIFACT |
Path or glob for .app bundle | Auto-detect |
IOS_DEFAULT_DEVICE |
Default simulator device | max |
IOS_DOWNLOAD_RUNTIME |
Auto-download missing runtimes (0/1) | 1 |
TEST_TUI |
Show process-compose TUI (true/false) | false |
BOOT_TIMEOUT |
Simulator boot timeout (seconds) | 120 |
TEST_TIMEOUT |
Overall test timeout (seconds) | 300 |
Configure the plugin by setting environment variables in devbox.json:
{
"env": {
"IOS_DEFAULT_DEVICE": "max",
"IOS_DEVICES": "min,max",
"IOS_DOWNLOAD_RUNTIME": "1"
}
}Key variables:
IOS_DEFAULT_DEVICE- Default device when none specifiedIOS_DEVICES- Comma-separated device names to evaluate (empty = all)IOS_APP_ARTIFACT- Path or glob for .app bundle (empty = auto-detect via xcodebuild + search)IOS_APP_SCHEME- Xcode scheme override (empty = auto-detect from project name)IOS_BUILD_CONFIG- Build configuration: Debug or Release (default: Debug)IOS_DERIVED_DATA_PATH- DerivedData directory (default: .devbox/virtenv/ios/DerivedData)IOS_DOWNLOAD_RUNTIME- Auto-download missing iOS runtimes (0/1, default: 1)
The plugin automatically discovers Xcode using multiple fallback strategies:
- Check
IOS_DEVELOPER_DIRenvironment variable - Check cache file (1-hour TTL)
- Find latest Xcode in
/Applications/Xcode*.appby version number - Use
xcode-select -poutput - Fallback to
/Applications/Xcode.app/Contents/Developer
Override discovery by setting IOS_DEVELOPER_DIR:
{
"env": {
"IOS_DEVELOPER_DIR": "/Applications/Xcode-15.4.app/Contents/Developer"
}
}In React Native projects or Android-only contexts, skip iOS environment setup:
{
"env": {
"IOS_SKIP_SETUP": "1"
}
}This speeds up shell initialization when you only need Android tooling. When set to 1, skips:
- Xcode path detection
- Device lock generation
- Environment configuration
Limit which devices are evaluated to reduce initialization time:
{
"env": {
"IOS_DEVICES": "min,max"
}
}After changing device selection, regenerate the lock file:
devbox run ios.sh devices evalDisplay all configuration settings:
devbox run ios.sh config showThis shows:
- Configuration directory
- Device definitions directory
- Scripts directory
- Default device
- Selected devices (from
IOS_DEVICES) - App artifact path (or auto-detect)
- Runtime download setting
Symptom: "Xcode developer directory not found" or Xcode tools unavailable.
Solutions:
-
Check if Xcode is installed:
xcode-select -p
-
Install Xcode from the App Store, then set the active developer directory:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
-
Or install command-line tools only:
xcode-select --install
-
Set explicit path in
devbox.json:{ "env": { "IOS_DEVELOPER_DIR": "/Applications/Xcode.app/Contents/Developer" } }
Symptom: "Runtime iOS X.X not found" or simulator won't start due to missing runtime.
Solutions:
-
List available runtimes:
xcrun simctl list runtimes
-
Download runtime via Xcode Settings:
- Open Xcode
- Go to Settings > Platforms
- Click "+" to download iOS Simulator runtimes
-
Enable auto-download in
devbox.json:{ "env": { "IOS_DOWNLOAD_RUNTIME": "1" } } -
Or download via command line:
xcodebuild -downloadPlatform iOS
Symptom: "CoreSimulatorService connection became invalid" or simulators won't start.
Solutions:
-
Restart CoreSimulatorService:
killall -9 CoreSimulatorService
-
Check service status:
launchctl list | grep CoreSimulator -
Kickstart the service:
launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService -
Open Simulator app to initialize:
open -a Simulator
Symptom: Error installing app on simulator or app doesn't launch.
Solutions:
-
Verify app bundle exists (check your build output directory):
find . -name '*.app' -type d -not -path '*/.devbox/*'
-
Check simulator is booted:
xcrun simctl list devices | grep Booted -
Verify bundle ID from a .app bundle:
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' /path/to/MyApp.app/Info.plist -
If auto-detection fails, set
IOS_APP_ARTIFACTexplicitly indevbox.json:{ "env": { "IOS_APP_ARTIFACT": "DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app" } }
Symptom: Xcode build errors related to linker flags or Nix environment variables.
Solution: The iOS init hook strips Nix compilation variables (LD, LDFLAGS, NIX_LDFLAGS, NIX_CFLAGS_COMPILE, NIX_CFLAGS_LINK) at shell startup, so xcodebuild works natively in devbox shell. If you still encounter issues, use the ios.sh xcodebuild wrapper which strips these variables in a subshell:
ios.sh xcodebuild -scheme MyApp buildSymptom: "Warning: devices.lock may be stale" or checksum mismatch.
Solution: Regenerate the lock file:
devbox run ios.sh devices evalCommit the updated lock file to version control.
Symptom: Simulator times out during boot or fails to start.
Solutions:
-
Increase boot timeout:
BOOT_TIMEOUT=180 devbox run start:sim
-
Check system resources (CPU, memory):
top
-
View simulator logs:
tail -f ~/Library/Logs/CoreSimulator/*/system.log
-
Check disk space:
df -h
-
Restart your Mac if simulators are consistently failing.
For detailed troubleshooting information, enable debug logging:
# iOS-specific debug
IOS_DEBUG=1 devbox shell
# Global debug
DEBUG=1 devbox shellDebug logs show:
- Environment variable resolution
- Xcode path discovery
- Device configuration loading
- Simulator startup commands
- App deployment steps
Check Test Logs:
Test logs are written to reports/:
# View all logs
ls -la reports/
# View specific process log (paths depend on your test suite configuration)
cat reports/ios-e2e-logs/build-app.log
cat reports/ios-e2e-logs/ios-simulator.log
cat reports/ios-e2e-logs/deploy-app.logCheck Simulator Status:
# List all simulators
xcrun simctl list devices
# Check if specific simulator is running
xcrun simctl list devices | grep "Booted"
# View simulator logs
tail -f ~/Library/Logs/CoreSimulator/*/system.logTest your app across multiple iOS versions:
-
Create device definitions for each version:
devbox run ios.sh devices create iphone_ios15 --runtime 15.4 devbox run ios.sh devices create iphone_ios17 --runtime 17.5 devbox run ios.sh devices create iphone_ios18 --runtime 18.0
-
Regenerate lock file:
devbox run ios.sh devices eval -
Sync simulators:
devbox run ios.sh devices sync
-
Test on each device:
devbox run start:sim iphone_ios15 # run your tests... devbox run stop:sim devbox run start:sim iphone_ios18 # run your tests... devbox run stop:sim
Use the plugin in GitHub Actions:
name: iOS CI
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Devbox
uses: jetify-com/devbox-install-action@v0.11.0
- name: Run E2E Tests
working-directory: examples/ios
env:
SIM_HEADLESS: 1
BOOT_TIMEOUT: 180
TEST_TIMEOUT: 600
IOS_DEFAULT_DEVICE: max
TEST_TUI: false
run: devbox run --pure test:e2eKey CI settings:
devbox run --pureensures isolated environmentTEST_TUI=falsedisables interactive TUI- Longer timeouts for slower CI machines
- Headless simulator mode (via
SIM_HEADLESS=1)
Switch between Xcode versions:
{
"env": {
"IOS_DEVELOPER_DIR": "/Applications/Xcode-15.4.app/Contents/Developer"
}
}Or use shell environment:
# Use Xcode 15.4 for this session
export IOS_DEVELOPER_DIR="/Applications/Xcode-15.4.app/Contents/Developer"
devbox shell
# Verify
devbox run ios.sh infoCreate simulators for various device types:
# iPhone devices
devbox run ios.sh devices create iphone15_pro --runtime 17.5
devbox run ios.sh devices create iphone14 --runtime 17.5
# iPad devices
devbox run ios.sh devices create ipad_pro --runtime 17.5
devbox run ios.sh devices create ipad_air --runtime 17.5
# Regenerate lock file
devbox run ios.sh devices evalThe device name in the JSON file should match the device type from xcrun simctl list devicetypes.
- Complete API Reference: See iOS Reference for exhaustive documentation of all commands, environment variables, and configuration options
- Architecture: See Architecture for script organization and layer documentation
- Plugin Testing: See plugins/tests/ios/ for plugin unit tests
- CI/CD Workflows: See CI/CD for CI integration examples
- Plugin Development: See Conventions for plugin development patterns
- Android Development Guide: Comprehensive Android development with the Android plugin
- React Native Guide: Cross-platform development with both iOS and Android plugins
- Device Management Guide: Deep dive into device definitions and management
- Testing Guide: Comprehensive testing strategies and best practices
- Troubleshooting Guide: Extended troubleshooting scenarios
- iOS Example: Complete iOS app with build scripts, deploy commands, and E2E test suites
- React Native Example: Cross-platform app using both iOS and Android plugins
- GitHub Issues: Report bugs or request features
- Devbox Documentation: jetify.com/devbox/docs
- Discord: Join the Jetify community for help and discussions