Deploy to physical iOS devices from the CLI

Deploy to physical iOS devices from the CLI

Deploy to physical iOS devices from the CLI

In about 10 minutes you’ll install FlowDeck, launch the interactive TUI (which walks you through trial activation on first run), open or scaffold a project, build and run it, and wire up AI agent skills. The whole loop, driven from the keyboard. No Xcode UI.

In about 10 minutes you’ll install FlowDeck, launch the interactive TUI (which walks you through trial activation on first run), open or scaffold a project, build and run it, and wire up AI agent skills. The whole loop, driven from the keyboard. No Xcode UI.

Before you start

This guide assumes:

  • FlowDeck is installed and your license or trial is active. If not, follow the getting-started guide first.

  • A physical iPhone, iPad, or Apple Watch you can connect by USB (or wirelessly after the initial USB pairing).

  • A working Apple Developer account. A free Personal Team is fine for installing on devices you own.

  • Your project's signing tab in Xcode is configured at least once (FlowDeck reads the signing configuration from your project; it doesn't generate it).

By the end you'll have built, installed, and launched your app on a real device, streamed its logs from the terminal, and have the workflow you'd use day to day instead of the Xcode Run button.

Pair the device (one-time setup)

The first time you connect a device, it has to be paired and trusted. This still happens through Xcode, FlowDeck doesn't bypass Apple's pairing flow.

  1. Plug the device into your Mac via USB.

  2. Open Xcode at least once.

  3. Go to Window → Devices and Simulators. Your device should appear in the sidebar.

  4. Trust the device when prompted on the iOS side ("Trust This Computer?").

  5. For wireless development, check "Connect via network" in the Devices pane while the device is still on USB. After that you can unplug.

After this one-time setup, FlowDeck can see the device for the rest of its life. You don't have to open Xcode again unless you re-pair.

Prefer the keyboard? Everything below works from FlowDeck's interactive TUI too, run flowdeck -i in your project and use single-letter shortcuts (D to pick the paired device, R to build + install + launch, L to stream logs, T for tests). The CLI snippets in the steps below are what the TUI runs under the hood; copy them when you want to script, hand them to an agent, or run one-off commands outside the TUI. See the TUI guide for the full keyboard reference.

Verify the device is reachable

List all paired devices:

flowdeck device list
flowdeck device list
flowdeck device list

You should see your device with its name, UDID, platform, and connection type (USB or network). Add --json for structured output if you're scripting against this.

If a device you paired in Xcode doesn't appear in flowdeck device list, the pairing didn't take. Re-trust it from the device's settings (Settings → General → VPN & Device Management → your computer name) and try again.

Build for the device

Build, sign, and produce an installable .app in one command. The destination is the device by name:

flowdeck build -D "Daniel's iPhone"
flowdeck build -D "Daniel's iPhone"
flowdeck build -D "Daniel's iPhone"

FlowDeck calls xcodebuild under the hood with the right destination string and signing configuration. If your scheme has multiple signing identities (debug vs release), pass --configuration Debug or --configuration Release explicitly. Save it to the project config so it sticks across commands:

flowdeck config set -D "Daniel's iPhone" -C Debug
flowdeck config set -D "Daniel's iPhone" -C Debug
flowdeck config set -D "Daniel's iPhone" -C Debug

If you have multiple devices on your account, you can use the partial name ("iPhone" matches the first paired iPhone) or pass the UDID directly. Be specific when you have multiple devices; partial matches lean toward the first paired device, which is rarely what you want.

Install and launch on the device

Single command, same as the simulator path:

flowdeck run -D "Daniel's iPhone"
flowdeck run -D "Daniel's iPhone"
flowdeck run -D "Daniel's iPhone"

This builds (if needed), signs, installs, and launches. The first launch on a personal development account may prompt you to trust the developer profile on the device, go to Settings → General → VPN & Device Management on the device and approve. After that first approval, subsequent launches don't prompt.

The output ends with the app's short ID, which you'll use to address the running app for logs and stops.

For install-without-launch (useful when you're handing off a build for someone else to test on their device):

flowdeck device install <udid> /path/to/MyApp.app
flowdeck device install <udid> /path/to/MyApp.app
flowdeck device install <udid> /path/to/MyApp.app

And uninstall when you want a clean slate:

flowdeck device uninstall <udid> com.example.myapp
flowdeck device uninstall <udid> com.example.myapp
flowdeck device uninstall <udid> com.example.myapp

Both device install and device uninstall take the device UDID positionally, not the device name. Grab the UDID from flowdeck device list. The higher-level flowdeck run -D "Daniel's iPhone" resolves names for you; the lower-level device install / uninstall commands don't.

Stream logs from the device

The same flowdeck logs command that streams simulator app logs also streams from physical devices. The launch type is recorded in the app registry, so FlowDeck routes through the device console pathway automatically, no separate command, no syntax change.

flowdeck apps
flowdeck logs <app-id

flowdeck apps
flowdeck logs <app-id

flowdeck apps
flowdeck logs <app-id

Output is your app's print() calls and OSLog messages, same as on the simulator. Wireless device connections work the same way; the underlying transport is handled by Apple's devicectl.

Or, to launch and stream in one step:

flowdeck run --log -D "Daniel's iPhone"
flowdeck run --log -D "Daniel's iPhone"
flowdeck run --log -D "Daniel's iPhone"

Stop or relaunch

Same surface as the simulator. When you're done with the app, stop it cleanly:

flowdeck stop <app-id
flowdeck stop <app-id
flowdeck stop <app-id

For a fresh run, stop then run. FlowDeck deliberately refuses to silently replace a running app, devices in particular can leave background activity that confuses the next launch if you don't terminate cleanly first.

flowdeck stop <app-id>
flowdeck run -D "Daniel's iPhone"
flowdeck stop <app-id>
flowdeck run -D "Daniel's iPhone"
flowdeck stop <app-id>
flowdeck run -D "Daniel's iPhone"

Patterns that come up most often

Switching between simulator and device on the same project.
The saved config holds one default target. To switch occasionally without losing the saved value, just pass the override: flowdeck run -S "iPhone 16" for the simulator, flowdeck run -D "Daniel's iPhone" for the device. To change the default permanently, flowdeck config set -D "..." (which clears the simulator default and vice versa).

Multiple devices on one account.
Pass distinct names: -D "Daniel's iPhone" vs -D "Daniel's iPad". If two devices share a name, target by UDID instead. Use flowdeck device list --json to script against device inventory in CI.

Apple Watch development.
For watchOS app development, pair the watch with its companion iPhone first (in Xcode's Devices pane). Then target the watch directly: flowdeck run -D "Daniel's Apple Watch". The watch must be unlocked and on the wrist for launches to complete.

Network-only devices.
After the initial USB pairing with "Connect via network" checked, the device shows up in flowdeck device list even when unplugged. The connection type column reads network. The Mac and the device must be on the same Wi-Fi network (or at least the same subnet) for the transport to work. Network builds and launches are slower than USB (binary transfer is the bottleneck) but work otherwise identically.

UI automation on physical devices.
Not supported. Apple does not expose the out-of-process accessibility hooks on physical devices that the iOS Simulator exposes. flowdeck ui simulator works only on the simulator. For device-based UI automation, XCUITest is the available option (it runs in-target on the device).

How the device workflow differs from the simulator workflow

For reference, what changes when you swap a simulator target for a physical device.

Aspect

Simulator

Physical device

Setup

None; simulators ship with Xcode

One-time pair + trust in Xcode, plus a developer profile

Targeting flag

-S "iPhone 16"

-D "Daniel's iPhone"

Build time

Fast (no signing step)

Slower (signing + binary transfer)

Install time

Instant

10-60s first install, near-instant after

Logs

flowdeck logs <app-id>

flowdeck logs <app-id> (same; FlowDeck routes the transport)

UI automation

flowdeck ui simulator fully supported

Not supported. XCUITest is the option for device UI testing.

Reset state

flowdeck simulator erase

flowdeck device uninstall (per-app) or factory-reset the device (rare)

Multiple targets

Cheap; spawn as many as you have RAM for

Cheap; pair as many as your team has, address by name or UDID

The CLI surface is intentionally identical where it can be. The unavoidable differences (signing, UI automation, install times) come from Apple's platform constraints, not FlowDeck.

When things go wrong

Device doesn’t appear in flowdeck device list

“Untrusted Developer” on first launch

“Could not launch” or signing errors

Wireless device disconnects mid-build

Logs are empty

App takes a long time to install on the device

“Provisioning profile doesn’t include this device”