Docker
The recommended way to run it. The image holds one Rust binary plus Debian's usbmuxd, and the container is not privileged.
Install
Pick any one of the three; they all end up with the same container.
Just a compose file
No need to clone the repository — the least effort. Create a directory (e.g. ~/iphone-control) and put a compose.yaml in it:
services:
iphone:
image: ghcr.io/derrors/iphone-control:latest # or pin a version, e.g. :0.1.0
container_name: iphone-control
# USB character devices only; no privileged needed
device_cgroup_rules:
- "c 189:* rmw"
init: true
stop_grace_period: 30s
network_mode: host
restart: unless-stopped
environment:
# All optional — uncomment what you need; see "Configuration" for the full list
# HTTP_PROXY: http://127.0.0.1:7890
# HTTPS_PROXY: http://127.0.0.1:7890
NO_PROXY: localhost,127.0.0.1,::1
# BARK_SERVER: https://api.day.app
# BARK_KEY: your-key
# TG_BOT_TOKEN: "123456:ABC..."
# TG_CHAT_ID: "your chat id"
volumes:
- /dev/bus/usb:/dev/bus/usb
- ./data:/var/lib/iphone-controlThen, in that directory:
docker compose up -d./data is relative to the directory holding compose.yaml; it contains the pairing records and the Apple ID session, so back it up. Instead of environment: you can put the variables in a .env next to it and add env_file: .env; if it holds notification keys, keep it out of git. See Configuration for what each variable does.
Build it yourself
git clone https://github.com/Derrors/iphone-control && cd iphone-control
cp .env.example .env
docker compose up -d --buildUse the prebuilt image
After a release, images are published at ghcr.io/derrors/iphone-control (amd64, arm64). In .env:
IMAGE=ghcr.io/derrors/iphone-control:latest # or pin a version, e.g. :0.1.0Then:
docker compose pull && docker compose up -d --no-buildAfterwards, run the setup wizard once.
What the compose file does
- Allows only USB character devices (
device_cgroup_rules: "c 189:* rmw") and bind-mounts/dev/bus/usb, so device nodes created after re-plugging show up in the container. network_mode: host— only so the host's proxy is reachable; nothing listens on a port.- Mounts
./dataat/var/lib/iphone-control, so pairing and login survive re-creating the container. init: trueandstop_grace_period: 30s— on stop it closes the sessions on the phone first, so no "paired computer" icon is left behind.
Upgrade
docker compose pull && docker compose up -d # just a compose file
# or
git pull
docker compose up -d --build # self-built
# or
docker compose pull && docker compose up -d --no-build # prebuilt imageIf the image tag pins a version, change it in compose.yaml to the new one before pulling.
Data lives in ./data and is untouched. Restarting the container drops the MCP connection in your AI client; reconnect it (/mcp in Claude Code).
Wait until it's ready
WDA is ready about 10 seconds after a restart. Several restarts in a row can make the phone ask for the passcode for "XCTest" again — just enter it.
Everyday commands
docker exec iphone-control iphone-control status # state
docker exec iphone-control iphone-control doctor # diagnosis
docker logs iphone-control # logs
docker compose down # stop (data is kept)