Emulator
Last updated 2026-09-21
plum-box-dev is the box's core running on your machine: the same binary, with mock isolation, a
seeded owner account and unsigned installs allowed. It is to a Plum Box what the iOS simulator is
to an iPhone — good enough for almost all of the work, and not the real thing.
Docker is optional. There are two ways to run it and one command for both:
| What runs | What you need | |
|---|---|---|
| native | the prebuilt core binary as an ordinary child process | nothing but plum-dev |
| docker | ghcr.io/plum-networks/plum-box-dev under docker compose |
a Docker daemon |
plum-dev emulator up picks Docker only when a Docker daemon actually answers; otherwise it
goes native by itself. --native and --docker force one. Everything after up — down,
logs, token, login, status — follows the mode up recorded, so stopping the emulator never
waits on a daemon that has gone away.
Native mode is a real answer on macOS too, not only Linux: the core builds for darwin (arm64 and amd64), so an Apple-silicon laptop runs it without Docker Desktop.
Start it
plum-dev emulator up # native unless Docker is running
plum-dev emulator login # waits for the seeded token, then saves the session
plum-dev push . --target emulator
The web UI is at http://127.0.0.1:8080 with the seeded owner:
| user | dev |
| password | plumbox-dev |
dev@plum.local |
On first boot the emulator writes a personal access token named plum-dev (emulator), with the
apps:install and apps:dev scopes, to <data>/emulator/pat.txt, and sets the box's trust level
to 3. That is what plum-dev emulator login reads — no pairing, no 6-digit code.
Override the seeded owner with PLUMBOX_DEV_OWNER_USER, PLUMBOX_DEV_OWNER_PASSWORD and
PLUMBOX_DEV_OWNER_EMAIL before the first boot (the seed runs once): in your shell for native
mode, in the compose file for Docker mode.
Native mode, in detail
plum-dev emulator up --native [--core-version 1.11.2] [--port 8080]
- It asks GitHub for the newest release of the public repository
plum-networks/plum-sdktaggedemulator-<core version>, or the one--core-versionpins. - It downloads the asset for your platform —
plum-server-<goos>-<goarch>, one oflinux-amd64,linux-arm64,darwin-amd64,darwin-arm64— and verifies its SHA-256 against theSHA256SUMSin the same release before running it. A mismatch deletes the file and stops there. - The binary is cached at
~/.cache/plum-dev/emulator/<version>/($XDG_CACHE_HOMErespected) and re-checked against the recorded digest on every start, so it is downloaded once. - It runs as a child process with
PLUMBOX_EMULATOR=1, keeping its data, logfile and pidfile under~/.local/share/plum-dev/emulator/($XDG_DATA_HOMErespected).
down sends SIGTERM and, if the core is still there 5 seconds later, SIGKILL. down --volumes
also deletes the data directory, which is how you reset the box. logs prints the last 200 lines
of the logfile, and -f follows it.
On a platform with no published asset — Windows, or anything not in the list above — up says so,
names the platform, and points you at Docker mode instead of failing obscurely.
Docker mode, in detail
plum-dev emulator up --docker [--pull]
up runs docker compose with the compose file that ships inside the CLI package. It pulls
ghcr.io/plum-networks/plum-box-dev:latest, names the container plum-box-dev, publishes
127.0.0.1:8080, and keeps two named volumes (/data/plum, /mnt/storage). If the pull
fails because you cannot reach that image, run plum-dev emulator up --native — it needs nothing
but the release assets.
--container <name> changes which container the commands act on (default plum-box-dev).
Commands
plum-dev emulator up [--native|--docker] [--core-version x.y.z] [--port 8080] [--pull]
plum-dev emulator down [--volumes] # --volumes also deletes the data
plum-dev emulator logs [-f] # without -f: the last 200 lines
plum-dev emulator token # print the PAT
plum-dev emulator login [--token …] [--box …] # default box http://127.0.0.1:8080
plum-dev emulator status # mode, core version, whether it is up
Once logged in, every box command takes --target emulator (or set PLUM_DEV_TARGET=emulator):
plum-dev push . --target emulator --logs
plum-dev logs dev.jan.notes -f --target emulator
plum-dev status dev.jan.notes --target emulator
What is different from a real box
| Emulator | Box | |
|---|---|---|
| Signatures | unsigned bundles are accepted | never |
| Architecture | host architecture allowed (amd64 runs amd64) | arm64 only |
| Isolation | mock — no per-app OS accounts, no uid switching | app-owned uid, cgroups |
| Transport | plain HTTP on :8080 |
TLS on :8443, relay, LAN certificate |
| Plum services | store, mail, auth, identity all off unless you set their URLs | configured |
plum-dev validate still enforces the real rules, so validate before you publish even when the
emulator let something through. --allow-host-arch downgrades the architecture error to a warning
for emulator builds; never publish such a binary. plum-dev build always targets arm64, which is
what the box and the store require.
Not available at all: Bluetooth onboarding, WiFi setup, the relay and remote access, push
notifications, and the NPU features (photo recognition). Available and worth testing here: the
control socket, the event channel, manifest clients[], and GET /api/apps/{id}/entitlement
(which answers with an empty list while no store is configured).
Useful environment variables
| Variable | Effect |
|---|---|
PLUMBOX_EMULATOR=1 |
the master switch inside the core: mock isolation, no mount check, Plum services off unless set, unsigned bundles allowed (native mode sets it for you) |
PLUMBOX_DEV_STORE_CATALOG |
path to a JSON catalog file used instead of the store, so you can exercise the store install path locally |
PLUMBOX_APP_CGROUPS=0 |
turn resource limits off (some container hosts cannot delegate cgroups); status then reports limits.reason |
PLUM_DEV_EMULATOR_MODE |
docker or native — decide once instead of per command |
PLUM_DEV_CACHE |
where downloaded cores live (default ~/.cache/plum-dev/emulator) |
PLUM_DEV_DATA |
where the emulator's data, log and pidfile live (default ~/.local/share/plum-dev/emulator) |
Running a core binary yourself
Native mode is exactly this, automated. If you already have a plum-server binary — the core
sources are not public, but the release assets and the container image both carry one — the manual
equivalent is:
PLUMBOX_EMULATOR=1 ./plum-server -port 8080 \
-data-dir ./demo-data -storage-dir ./demo-storage -dev-skip-mount-check
cat ./demo-data/emulator/pat.txt # the seeded token
plum-dev login --box http://127.0.0.1:8080 --token plum_pat_…
Everything above about the seeded account and the relaxations applies the same way.