Install an app: directories, image, diff, commit#
You will end up with a container running on the router, declared in the configuration tree, with its own nav entry and a link to its web interface. Because it is declared in the tree, the install is a diff you review, a commit you can roll back, and state that survives an image upgrade.
That is the whole idea: an app is not an opaque package with its own lifecycle. It is configuration.
Before you start#
- The operator role and a licence.
- Container support on the router, and a route to the registry.
- The agent running on the router for step 1 — see below.
The three steps, and only one of them is configuration#
VyOS validates a container at commit time and rejects it if either prerequisite is missing. Both are actions, not configuration, so they happen before the diff exists.
| Step | What it does | Why it is separate |
|---|---|---|
| 1 · Directories | mkdir -p each volume source | VyOS: Volume "conf" source path "…" does not exist |
| 2 · Pull image | add container image <ref> | VyOS: Image "…" does not exist locally |
| 3 · Stage install | set container name … into the Commit Bar | This is the only part that is configuration |
Step 1 — Choose an entry#
Services → Apps. The catalogue is 38 entries — 36 containers and two built-in feature modules — grouped by category: DNS and filtering, VPN and remote access, monitoring, security, network services, management.
Each entry declares what it is, why you would put it on a router specifically, its ports, its volumes, its environment variables, and its notes: the things that will bite you, like a port-53 conflict or a need for host networking.
A feature module is different. kind: "builtin" marks a native VyOS feature whose
Wheelhouse page is gated by installation — WireGuard and Suricata IDS. Installing one is
instant and stages nothing: there is no image, no volume and no commit, because the flag is
a UI-surface preference in the agent's own state rather than router behaviour. And
configuration outranks the flag: if the feature is configured on the router by anything at
all, its page shows regardless.
Step 2 — Fill in what it needs, and read the plan#
The install panel takes a container name, an address on the apps network, the volume
sources, and any environment variables the entry declares. POST /api/apps/plan renders
the operations without staging them, and the panel shows them.
set container network apps prefix 10.99.0.0/24
set container name adguard image docker.io/adguard/adguardhome:latest
set container name adguard network apps address 10.99.0.5
set container name adguard volume work source /config/apps/adguard/work
set container name adguard volume work destination /opt/adguardhome/work
set container name adguard volume conf source /config/apps/adguard/conf
set container name adguard volume conf destination /opt/adguardhome/confThe container network line appears only when the network does not already exist and the app
is not on host networking. The prefix 10.99.0.0/24 is the catalogue's own.
The plan also carries warnings, and they are worth reading:
| Warning | Means |
|---|---|
| Runs on the host network: it shares the router's interfaces and any port it binds is exposed on the router itself. | The container has no separate address. Treat its ports as the router's ports. |
| Runs privileged. It can reach the router's kernel interfaces directly — install it only if you trust the image. | What it says. |
| No address chosen — pick one inside 10.99.0.0/24 before committing. | Fill in the address field. |
| Environment variable X is required and has no value. | The entry marks it required; the container will not work without it. |
The agent validates what you typed before VyOS would: a container name is letters, digits and hyphens only, and a volume source must be an absolute path with no spaces or shell characters. Those are refused here, where the message can name the field, rather than at commit time where VyOS would only say "Set failed".
Step 3 — Commit the diff#
Read it. This is the step the whole model exists for.
Step 4 — Use it#
Once it is running, the entry's integration declarations take effect:
nav_labelgives the app its own left-nav entry under Installed, with a dot when it is declared but not running.web_uigives it an Open link built from the container's real address.healthmakes the agent probe the port — TCP, plus an HTTP request when the entry gives a path. Deliberately not an app-specific API call: the product does not claim knowledge of an app's internals it does not have.hintsput an offer on another Wheelhouse page, with the app's real address substituted in and the exact operations attached. Install AdGuard and the DNS page grows a card offering to point the resolver at it, with a one-click Stage this change.
Hints stage; they never apply. The Commit Bar is still the only thing that commits.
Check it worked#
Apps → Installed shows declared beside running, with the health verdict.
show containerThe health probe is honest about what it does not know. In off-router development mode the agent has no route to the container bridge, so a failed probe would say nothing about the app — it reports not probed — this agent runs off-router rather than a red "not answering" that would be a lie.
The app's logs are on its detail page, or:
sudo podman logs adguardWhen the pull fails#
| Symptom | Cause |
|---|---|
toomanyrequests | Docker Hub rate-limits anonymous pulls per source address. Retry later, or pull the image by hand on the router. There is no registry mirror or credential setting. |
| Image "…" does not exist locally at commit | Step 2 did not happen or did not finish. |
| Volume "…" source path does not exist at commit | Step 1 did not happen. Off-router, run the mkdir -p the agent handed you. |
Undoing it#
Stop, uninstall, and what stays behind.
See also#
- Update check and restart
- Network-wide filtering with AdGuard Home — a worked example
- Join a tailnet from the router — a privileged, host-network example
- Apps are configuration
- Apps — catalogue
Checked against docs/apps.md ·
agent/apps.go ·
agent/catalog.go ·
agent/catalog.json ·
ui/src/pages/Apps.tsx