Wheelhouse docs

One app#

Every installed app has its own page at /apps/<container name>, reached from a catalogue card's Manage button, from the installed grid, from the Dashboard, or from the app's own entry in the left navigation. It is where the lifecycle lives.

The important thing about this page is what it is not: there is no control socket behind it. Stopping an app stages set container name <x> disable; starting it deletes that node; uninstalling deletes the subtree. App state is in the same diff-and-commit model as everything else on the router, which means rolling back a revision takes the apps with it. Only two buttons act immediately — Check for update and Restart — and both say so.

The header#

The title is the catalogue name, the subtitle its blurb, and the path badge reads container name <x> — the subtree this whole page describes. Then a tab strip (Overview, Logs, Config) and the actions.

ActionRoleImmediate or stagedWhat it does
Open ↗anyOpens the app's web interface, when the entry declares one and the container has an address.
Check for updateoperatorimmediateRe-pulls the image and reports whether anything changed.
Restartoperatorimmediate (two commits)Bounces the container.
Stop / StartoperatorstagedSets or deletes disable.
UninstalloperatorstagedDeletes the container subtree, and the shared network when it was the last one on it.

Check for update and Restart are only offered while the app is running.

Overview#

Four stat tiles and four panels.

TileValue
StateRunning / Stopped / Not running, with podman's own status underneath
ReachableYes / No / Not probed, with the probe note underneath
AddressThe container address, or host
ImageThe image name, with its tag underneath

Runtime carries the container name, the full image reference, the image ID, build date and size (matched from the images table), the networking mode, the ports podman reports, the status, and the web-UI link when there is one.

Environment lists the variables the configuration sets, with credential-looking values masked. The panel says where to change them: the Config tree page, because this page does not edit environment.

Storage lists the volumes as source → destination. When there are none it says the consequence rather than the fact: "No volumes. Anything this app writes is lost when the container is recreated."

About is the catalogue entry's own description, its closes line when it fills a documented platform gap, badges for host networking, privileged and each declared capability, a link to the upstream documentation, and the entry's notes — the things that will bite you, written per app.

A fifth panel, Where this app shows up elsewhere, lists the hints this entry offers and links to the page each appears on. See integration offers.

Two banners#

Drift. When the configuration declares the container, it is not disabled, and podman is not running it:

The config declares this container but podman is not running it. Usually the image is missing, a required environment variable is unset, or it crashed on start — the Logs tab will say which.

A newer image is present. After an update check that found one:

A newer image is on the router (<old id><new id>), but the running container still holds the old one. Restart to pick it up.

Below the banners, a bridged app gets one more line: it is on the container bridge, reachable from the router, and LAN clients need a route or a forward to reach it.

Check for update#

POST /api/apps/update with the container name. The handler — agent/apps.go, handleAppUpdate — does four things:

  1. Reads the local image ID for that reference, past the cache.
  2. Re-pulls the image through the router's container-image endpoint. Podman only downloads layers that moved, so "already current" is cheap to discover.
  3. Reads the local image ID again, again past the cache.
  4. Answers with updated, the old and new IDs, and restart_required, which is true only when the image changed and the container is running.

The cache bypass in steps 1 and 3 is not incidental: a pull that took longer than the 3-second op-mode TTL used to have "before" and "after" served from the same stale entry, and a real update reported updated: false.

The toasts are correspondingly careful. Changed: "New image pulled — restart the app to run it; the current container still holds the old one." Unchanged: "Already current." A pull is not an upgrade, and this page does not pretend it is.

The action is audited as app-update with the container name and the image reference.

Restart#

POST /api/apps/restart. This is a two-commit bounce: the agent commits set container name <x> disable, then commits delete container name <x> disable. That is the only restart the router's API can express — there is no restart operation, and reset container is rejected.

Consequences worth knowing before you press it:

  • It is two real commits. They appear in the commit history and both are audited as app-restart.
  • It does not touch the working set. The staging area lives in the agent, and each of those Configure calls carries only its own operation, so a restart does not sweep up whatever else you had staged.
  • A stopped app refuses with 409: "<x> is stopped — start it instead of restarting it."
  • If the second commit fails, the app is down and the error says so"restart stopped the app but could not start it again — it is DOWN" — rather than the useless "restart failed".

Stop, start and uninstall#

All three stage; none of them act. The toast after staging says what will happen when you commit.

# Stop
set container name adguard disable

# Start
delete container name adguard disable

# Uninstall
delete container name adguard

Uninstall adds one more operation in exactly one case: when the app being removed is the last bridged container on the router, it also stages delete container network apps, so an empty store leaves no residue in the configuration tree. Host-networked apps are not counted for that test, because they were never on the bridge.

Logs#

The Logs tab reads GET /api/apps/logs/<name>, which is show container log <name> on the router, split into non-empty lines. It polls every 5 s while Following is on and shows a live indicator that goes dark if a response is more than 12 s old. Pausing stops the poll entirely.

The container name in the URL is validated against the container-name pattern before it reaches the router — letters, digits and hyphens — so a path segment cannot become part of a command.

This is the fastest answer to a drift badge: a container that crashed on start has said why here.

Config#

The Config tab prints the container's configuration subtree as JSON, exactly as the router returned it. It is a read; editing happens through the Config tree page or the CLI.

An app that is not there#

Navigating to /apps/<name> for a container that is not declared gives an empty state — "No such app on this router" — and a link back to the catalogue, rather than a blank page or an error.

See also#


Checked against ui/src/pages/AppDetail.tsx, agent/apps.go, agent/main.go, agent/vyos.go, docs/apps.md.

Updated 2026-09-02 manual apps containers lifecycle