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
email 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]
  1. It asks GitHub for the newest release of the public repository plum-networks/plum-sdk tagged emulator-<core version>, or the one --core-version pins.
  2. It downloads the asset for your platform — plum-server-<goos>-<goarch>, one of linux-amd64, linux-arm64, darwin-amd64, darwin-arm64 — and verifies its SHA-256 against the SHA256SUMS in the same release before running it. A mismatch deletes the file and stops there.
  3. The binary is cached at ~/.cache/plum-dev/emulator/<version>/ ($XDG_CACHE_HOME respected) and re-checked against the recorded digest on every start, so it is downloaded once.
  4. It runs as a child process with PLUMBOX_EMULATOR=1, keeping its data, logfile and pidfile under ~/.local/share/plum-dev/emulator/ ($XDG_DATA_HOME respected).

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.