Install with Docker
The v0.1.8 container is a multi-platform Linux image containing one static controller binary, CA roots, licenses, and release material. The final image has no shell or package manager.
Use these artifact instructions only after the completed exact v0.1.8 tag workflow and GitHub release establish publication. Source preparation, a version string, or a tag alone does not prove that the image is available.
Outcome: oonfeeWRT runs as non-root in a hardened container, publishes HTTP on host loopback by default (or one explicitly selected management address), and persists state in a named Docker volume.
Prerequisites
- Docker with Compose support.
- A Linux amd64/arm64 host or Docker Desktop capable of running the Linux image.
- Permission to run Docker as a non-root host user.
- Controller-host network reachability to each router.
curl,/dev/urandom,base64,sudo, and a private working directory.
Write impact: With a new named volume, these steps create host/container state and do not contact a router. Reusing an existing controller volume resumes adopted-device polling and may resume automatic runtime 802.11k neighbour maintenance.
1. Create the deployment directory
install -d -m 0700 oonfeewrt
cd oonfeewrt
umask 0772
3
Keep docker-compose.yml and passphrase in this private directory. The controller database itself lives in Docker's named oonfee-data volume.
2. Download the release Compose file
curl --fail --location \
--output docker-compose.yml \
https://raw.githubusercontent.com/aiden0rchad/oonfeeWRT/v0.1.8/deploy/docker-compose.yml2
3
The file pins the image version through the required OONFEE_VERSION value instead of silently following latest.
3. Create the runtime passphrase
head -c 32 /dev/urandom | base64 > passphrase
sudo chown 65532:65532 passphrase
sudo chmod 600 passphrase2
3
The Compose service runs as UID/GID 65532, so the bind-mounted mode-0600 file must belong to that UID. A missing source path is an error; Compose is configured not to create an empty directory in its place.
This is the controller runtime/boot passphrase, not an owner account password. Store a protected recovery copy separately.
4. Pin and start v0.1.8
Store the image tag and host publish address in .env so every Compose lifecycle command uses the same values:
printf '%s\n' \
'OONFEE_VERSION=v0.1.8' \
'OONFEE_HTTP_BIND=127.0.0.1' > .env
chmod 600 .env
docker compose up -d2
3
4
5
The release image is:
ghcr.io/aiden0rchad/oonfeewrt:v0.1.8The supplied service hardening includes:
- process UID/GID 65532;
- read-only root filesystem;
- all Linux capabilities dropped;
no-new-privileges:true;- private
noexec,nosuid,nodevtmpfs; - state mounted at
/data; - host-loopback mapping
127.0.0.1:8080:8080by default; - 150-second stop grace period for an in-flight rollback/confirm cycle.
5. Verify the service
docker compose ps
docker compose logs --tail=100 oonfeewrt
docker compose exec oonfeewrt /oonfeewrtd -version
curl --fail http://127.0.0.1:8080/healthz2
3
4
The health request should print:
okOpen http://127.0.0.1:8080 on the controller host and create the first owner.
Choose where Docker publishes HTTP
The v0.1.8 Compose file keeps loopback as the safe default and accepts OONFEE_HTTP_BIND, a Compose-only host publish IP. It does not replace OONFEE_LISTEN; the container must continue listening on :8080 internally.
To reach the controller from a trusted management LAN, change OONFEE_HTTP_BIND in .env to the controller host's specific LAN address, then recreate the service:
docker compose up -d
curl --fail http://192.168.1.20:8080/healthz2
If you do not use .env, repeat both variables on every Compose lifecycle command. Without either, a later recreate can return to the loopback default or fail because the required image version is unset.
Use the controller's real address in the browser. 0.0.0.0 is a bind target, not a browser URL. To deliberately publish on every host IPv4 interface:
OONFEE_HTTP_BIND=0.0.0.0 docker compose up -dThe controller has no native TLS listener. Prefer one management IP over 0.0.0.0, enforce host/network firewall policy, and never expose port 8080 directly to the Internet. During first-owner setup, browse to a literal IP; DNS names are deliberately rejected at that one-time boundary to prevent DNS rebinding. Existing deployments keep their current mapping until their local Compose file is replaced or edited; pulling an image alone does not rewrite it.
Container networking and discovery
The default bridge mode is the safe portable choice. Polling, adoption, and Apply are normal outbound layer-3 connections and work when the container can route to the router.
What bridge mode does not provide is the controller host's LAN layer-2 view:
- add-by-address works;
- the automatic scan normally sees only the container's interface subnets;
- the shipped discovery scan does not use ARP or mDNS in any network mode.
On Linux, host networking can expose the host's eligible LAN interfaces to the bounded TCP /ubus scan. It does not enable a separate layer-2 discovery engine. In docker-compose.yml, remove ports:, add:
network_mode: hostand set:
OONFEE_LISTEN: "127.0.0.1:8080"Host mode exposes the daemon directly according to its listen address. Review host firewall and TLS policy before using a non-loopback bind. Docker Desktop users should use add-by-address.
Optional: verify the published image signature
Install cosign using Sigstore's official instructions, then verify the v0.1.8 GitHub Actions identity:
cosign verify \
--certificate-identity "https://github.com/aiden0rchad/oonfeeWRT/.github/workflows/release.yml@refs/tags/v0.1.8" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/aiden0rchad/oonfeewrt:v0.1.82
3
4
Deployments should pin v0.1.8 or the reported digest. The 0.1.8, 0.1, and latest aliases may resolve to the same manifest but are not immutable deployment intent.
Optional: run without Compose
This uses bind-mounted state owned by your non-root host user:
[ "$(id -u)" -ne 0 ] || { echo "run Docker as a non-root user" >&2; exit 1; }
install -d -m 0700 \
"$HOME/.config/oonfeewrt" \
"$HOME/.local/share/oonfeewrt-container"
umask 077
head -c 32 /dev/urandom | base64 > "$HOME/.config/oonfeewrt/passphrase"
chmod 600 "$HOME/.config/oonfeewrt/passphrase"
docker run -d \
--name oonfeewrt \
--restart unless-stopped \
--read-only \
--user "$(id -u):$(id -g)" \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--stop-timeout 150 \
--tmpfs "/tmp:rw,noexec,nosuid,nodev,uid=$(id -u),gid=$(id -g),mode=0700" \
-p 127.0.0.1:8080:8080 \
--mount "type=bind,source=$HOME/.local/share/oonfeewrt-container,target=/data" \
--mount "type=bind,source=$HOME/.config/oonfeewrt/passphrase,target=/run/secrets/oonfee-passphrase,readonly" \
-e OONFEE_DATA_DIR=/data \
-e OONFEE_LISTEN=:8080 \
-e OONFEE_PASSPHRASE_FILE=/run/secrets/oonfee-passphrase \
ghcr.io/aiden0rchad/oonfeewrt:v0.1.82
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Stop and restart safely
Compose sends SIGTERM and allows 150 seconds for shutdown:
docker compose stop
docker compose start2
The longer grace period lets an Apply that reached OpenWrt's rollback-protected stage finish its confirm decision. Do not force-kill the container during an Apply unless the host itself is failing.
Troubleshooting and recovery
The passphrase mount is denied
For the supplied Compose service:
sudo chown 65532:65532 passphrase
sudo chmod 600 passphrase2
Confirm that passphrase is a file, not a directory.
The container repeatedly restarts
Inspect the startup error:
docker compose logs --tail=200 oonfeewrtCommon causes are a missing/unreadable passphrase, the wrong passphrase for the volume's keyring, a missing keyring beside an existing database, an unsupported database downgrade, or port 8080 already in use.
Discovery finds no router
Use Adopt a device and enter the router IP. The bounded TCP /ubus scan sees only eligible, reachable interface subnets; a container bridge or Docker Desktop VM commonly exposes only its internal subnet. The scanner does not use ARP or mDNS.
The controller appears empty after recreating the service
Check that the same oonfee-data volume is mounted at /data. A new volume creates a new controller. Do not copy only oonfeewrt.db; restore its matching keyring.json and use a consistent snapshot.
The browser cannot connect from another computer
The default mapping is intentionally loopback-only. Set OONFEE_HTTP_BIND to the controller's isolated management-LAN IP; otherwise add trusted reverse-proxy TLS. Do not publish raw port 8080 to the Internet.
Avoid accidental deletion
This removes the container but preserves the named volume:
docker compose downThis also deletes the named volume and controller state:
docker compose down -vUse -v only after a verified backup and only when deletion is intentional.