Docs / Reference / Mobile app — build & install
Documentation

Mobile app — build & install

🌐 **English** · [简体中文](mobile-app.zh.md) · [فارسی](mobile-app.fa.md) · [Español](mobile-app.es.md) · [Português](mobile-app.pt-BR.md) · [日本語](mobile-app.ja.md) · [한국어](mobile-app.ko.md) · [Français](mobile-app.fr.md) ·

6 min read Updated Jun 2, 2026 Source Edit History

Mobile app — build & install#

🌐 English · 简体中文 · فارسی · Español · Português · 日本語 · 한국어 · Français · Deutsch · Русский

The opendray mobile app (app/mobile/) is a control client, not a second gateway. It does the same job as the web admin at /admin/: spawn and drive sessions, manage channels and integrations, browse memory, read git hosts. The agents themselves keep running on your gateway host — the phone just attaches to them.

Because of that, the app is useless on its own: it connects to a running opendray gateway over HTTPS. Get the gateway up first (getting-started), then build the app and point it at your gateway URL.

Why no App Store / Play Store download? opendray is self-hosted, single-tenant software. A store build would have to bake in someone's backend, which is exactly what opendray is not. So you build the app yourself, signed with your own identity, and it talks only to your gateway. The two supported paths below are (A) an Android APK you sideload, and (B) an iOS build you install through Xcode.


Step 0 — make the gateway reachable from the phone#

The app talks to the gateway over the network, so the phone has to be able to reach it.

Scenario What to enter as Gateway URL
Phone on the same LAN as the gateway http://<gateway-lan-ip>:8770 (e.g. http://192.168.1.50:8770)
Gateway behind a reverse proxy with TLS https://opendray.yourdomain.com
Off-LAN access (cellular, travelling) A public HTTPS endpoint — Cloudflare Tunnel, Tailscale, or an nginx/Caddy reverse proxy

Don't expose :8770 raw to the internet. Put TLS and an ingress in front of it. Cloudflare Tunnel is the lowest-friction option (no port-forwarding, no public IP). nginx / Caddy snippets — including the WebSocket upgrade headers the Sessions terminal needs — are in operator-guide §Topology.

Verify reachability from the phone before building, e.g. open the Gateway URL in the phone's browser — you should get the web admin login page.


Step 1 — install the Flutter toolchain#

The app is built with Flutter. You need it on the machine that does the build (not the phone).

[object Promise]

flutter doctor is the gate: it tells you exactly what's missing for Android (Android SDK + a device/emulator) or iOS (Xcode + CocoaPods). Fix the ✗ lines for your target platform before continuing.

Fetch dependencies once:

[object Promise]

Step 2A — Android: build an APK and sideload it#

This is the simplest path — no developer account, no store.

Build the APK#

[object Promise]

Output lands in:

[object Promise]

Signing note. Out of the box, the release build is signed with the debug keystore (see the TODO in android/app/build.gradle.kts). That's fine for personal sideloading. If you want a proper upload key (required for Play Store, and good hygiene for a build you'll keep updating), follow Flutter — Sign the app and add a signingConfig for release.

Get the APK onto the phone#

Pick whichever is convenient:

[object Promise]

Or transfer the .apk file to the phone (AirDrop-equivalent, a file share, a download link, email to yourself) and tap it. Android will ask you to allow "Install unknown apps" for whichever app is opening the file (Files, Chrome, etc.) — grant it, then confirm the install.

The app appears as Opendray (io.opendray.opendray).


Step 2B — iOS: build and install through Xcode#

iOS has no sideload-an-APK equivalent — every install is code-signed. You need a Mac with Xcode and an Apple ID. A free Apple ID works (the app is re-signed every 7 days; you reinstall when the provisioning profile expires). A paid Apple Developer account (US$99/yr) gives year-long profiles and TestFlight.

One-time signing setup#

[object Promise]

In Xcode:

  1. Select the Runner target → Signing & Capabilities tab.
  2. Tick Automatically manage signing.
  3. Team: pick your Apple ID team (add your Apple ID under Xcode → Settings → Accounts if it's not listed).
  4. Bundle Identifier: it ships as io.opendray.opendray. With a free Apple ID this exact ID may already be taken on Apple's side — if Xcode shows a provisioning error, change it to something unique like io.opendray.opendray.<yourname>.

Build & install onto the iPhone#

  1. Connect the iPhone via USB; trust the computer when prompted.
  2. Enable Developer Mode on the phone: Settings → Privacy & Security → Developer Mode → on → reboot.
  3. In Xcode's device dropdown (top bar), select your iPhone.
  4. Press ▶ Run (or ⌘R). Xcode builds, signs, and installs.

Or drive it from the CLI and let Xcode handle signing:

[object Promise]

First launch on the device#

iOS won't run an app signed by a personal team until you trust the developer profile:

  • On the phone: Settings → General → VPN & Device Management → your Apple ID → Trust.

The app appears as Opendray on the home screen.

Free Apple ID expiry. After ~7 days the app stops launching ("could not verify app"). Re-run the build from Xcode to refresh the profile. A paid account avoids this.


Step 3 — connect the app to your gateway#

First launch shows the onboarding screen:

  1. Gateway URL — enter the URL from Step 0 (e.g. https://opendray.yourdomain.com). Tap Continue.
  2. Sign inadmin + your admin password (the one you set in [admin].password, or changed to afterward).

That's it — you land on the same surfaces as the web admin: Sessions, Channels, Integrations, Memory, Git, Settings.

To point the app at a different gateway later, tap Change on the login screen (or Settings → server) and re-enter the URL.


Updating the app#

There's no auto-update — you reinstall after pulling new code:

[object Promise]

The app's own version string lives in app/mobile/pubspec.yaml (version: <semver>+<build>).


Troubleshooting#

Symptom Cause Fix
Onboarding "could not connect" Phone can't reach the Gateway URL Open the URL in the phone's browser; fix LAN IP / tunnel / TLS first (Step 0)
Login works but Sessions terminal never connects Reverse proxy is dropping the WebSocket upgrade Add WS headers — operator-guide §Topology
Android blocks the install "Install unknown apps" not granted Allow it for the app that opens the .apk (Files / Chrome)
iOS "Untrusted Developer" on launch Personal-team profile not trusted yet Settings → General → VPN & Device Management → Trust
iOS "Unable to install / signing" in Xcode Bundle ID clash with a free Apple ID Change Bundle Identifier to io.opendray.opendray.<yourname>
iOS app stops opening after a week Free Apple ID profile expired (7 days) Re-run from Xcode, or use a paid account
flutter doctor shows ✗ for your platform Missing Android SDK / Xcode / CocoaPods Follow the exact line flutter doctor prints

See also#