docker discovery - mescon/Muximux GitHub Wiki
Connect Muximux to a Docker daemon and it can enumerate running containers, propose them as apps, and keep their URLs current as IPs shift across restarts. Off by default because it requires read access to the Docker socket.
- Scan: lists running containers and proposes a name, icon, URL, port, and group for each, with confidence ratings (high / medium / low).
- Import: one-click adds the chosen containers as apps in the menu, gateway subdomains, or both. Each row picks a routing mode: Direct URL, internal Proxy, or Gateway domain.
- Refresh: a background poller (default 60s) re-resolves each tracked container against the daemon and rewrites the saved URL if the container's IP changes. Caddy reloads once per tick when gateway-site URLs change.
-
Auto-import (optional): with
discovery.docker.auto_importset toadd/update/sync, Muximux imports every container that carries at least onemuximux.*label (amuximux.discovery.idalone is enough) and does not carrymuximux.app.enabled=false, with no modal and no click. Unlabelled containers are never auto-imported.addimports each new labeled container once;updatealso re-syncs an imported app when its labels change;syncadditionally removes an imported app when its container disappears or opts out. Off by default. See Automatic Import. - Detach / Re-link: per-row controls in Settings → Discovery let you stop auto-managing an entry or re-point it at a different container when you migrate daemons.
Upgrading from 3.5 or earlier? Read Upgrading to 3.6 first.
✅ You run apps as Docker containers on the same host as Muximux (or on hosts Muximux can reach over the Docker API) ✅ Container IPs change across restarts (default Docker bridge networking) and you don't want to chase them by hand ✅ You're comfortable granting Muximux read access to the Docker socket
❌ Your apps don't run in Docker (the scanner has nothing to enumerate) ❌ Your containers always have stable IPs / hostnames (manual app entries are simpler) ❌ You can't or don't want to expose the Docker socket to Muximux (this feature is opt-in; everything else works without it)
-
Settings → Discovery: tick Enable Docker discovery, pick a Network strategy, click Test connection. The banner turns green when Muximux can reach the daemon and identify enough about itself to use the chosen strategy.
-
Apps tab (or Gateway tab) → Discover from Docker: the modal lists every running container the daemon returned (Muximux's own container is excluded so you can't accidentally import yourself as an app). Each row shows:
- Suggested name, icon, group (catalog-recognised images get these auto-filled at "medium" confidence)
- Stable tracking key (operator label > Swarm service > Compose service > container name > container ID)
- Resolved URL preview
- Stability warning when the key is likely to break on a
docker-compose --force-recreateor swarm task reschedule - A Not importable chip when auto-import would skip the container, with the reason (see Why a container is skipped)
The catalog matcher is lenient about operator prefix conventions: a container named
homelab-sonarr,homelab_radarr, orprod.plexstill picks up the matching catalog entry just like a baresonarr/radarr/plexwould. Tokens are split on-,_, and.; comparison is exact-token (sotransmissionicdoesn't masquerade astransmission). Multi-word app names likehome-assistantwork via an adjacent-pair fallback. -
Pick routing per row and click Import:
-
Direct - menu links to
http://<container-ip>:<port>directly -
Proxy - menu links via Muximux's
/proxy/<slug>path-prefix reverse proxy -
Gateway domain - menu links to
https://<your-subdomain>; requires also creating the gateway site in the same row
If a row's group does not exist yet, the import creates it in the same save (see Groups are created automatically).
A container that is already tracked shows a Tracked chip in the Discover dialog, naming what tracks it (an app, an auto-imported app, a gateway site or a quarantined entry, plus the endpoint when it differs). Its row cannot be selected, since it cannot be imported again. A row whose name is used by another app shows "Name already in use by another app".
-
Direct - menu links to
-
Settings → Discovery → Currently tracked: every imported app or gateway site appears here. Per-row Detach stops auto-management; Re-link appears when the saved
DockerEndpointno longer matches the configured endpoint (typical after a daemon migration). A row shows "Container missing since " while its container cannot be found on the daemon. Invalid Docker-owned entries that were not loaded are listed below the table; see Quarantined entries.
discovery:
docker:
enabled: true
endpoint: unix:///var/run/docker.sock # or tcp://host:2376
tls: # only needed for tcp:// with mTLS
enabled: false
ca_cert: "" # path to CA bundle that signed the daemon cert
client_cert: "" # path to client cert (PEM)
client_key: "" # path to client key (PEM, chmod 600)
network_strategy: container_ip # see "Strategies" below
network_filter: "" # optional: scope scans to one network
host_ip: "" # required by host_port strategy
refresh_interval: 60s # poller cadence, [10s, 1h]
auto_import: off # off (default) | add | update | sync (3.2.0)
require_explicit_enable: false # only containers with muximux.app.enabled=true (3.6.0)
lifecycle_enabled: false # allow start/stop/restart of tracked containers (needs :rw socket)
lifecycle_min_role: admin # min role for lifecycle controls
lifecycle_allowed_groups: [] # additionally require group membership
health_badge_placement: overview # overview | overview_and_nav | offHow you grant Muximux access to the Docker daemon depends on how you run Muximux. The endpoint: field in config.yaml is auto-populated to the platform default if you leave it blank, so most operators only need the OS-level access step below.
services:
muximux:
image: ghcr.io/mescon/muximux:latest
# ... usual ports / volumes / env ...
volumes:
- ./data:/app/data
# :ro = discovery only (read-only). Use :rw instead to also enable
# start/stop/restart controls -- see "Container lifecycle controls" below.
- /var/run/docker.sock:/var/run/docker.sock:roThat's the whole prerequisite. The entrypoint stats the bind-mounted socket at startup, reads its group ownership from the host, and adds the runtime user to a matching group inside the container - so the non-root muximux process can actually read the socket. No DOCKER_GID env var, no group_add, no hunting for the right number. The default endpoint: unix:///var/run/docker.sock already matches the mount.
The trailing :ro is the default and keeps Muximux read-only: with :ro it can see and list your containers but cannot start, stop, or change any of them. Marking the mount read-only means a compromised Muximux can't pivot to full daemon control. The Docker engine API still exposes enough surface to enumerate every container on the host, so treat this mount as "effective host root for read" and gate the dashboard accordingly with auth.
Container lifecycle controls (start / stop / restart, see below) are opt-in through two layers: the socket must be mounted :rw and discovery.docker.lifecycle_enabled: true must be set. Neither one alone is enough - the dashboard still won't start, stop, or restart anything until you turn on both. Even with both on, Muximux can only start, stop, and restart the containers it already tracks - it can't pull or build images, create or delete containers, run commands inside them, or touch networks and volumes (Muximux never calls those parts of the Docker API). Every action, and every blocked attempt, is written to the audit log.
For rootless Docker, docker-socket-proxy sidecars, or custom socket paths, two override env vars are available: DOCKER_SOCKET (the path the entrypoint stats) and DOCKER_GID (bypass auto-detect and force a specific GID). Almost no one needs these.
There's no container in this path, so no entrypoint script. Linux's normal socket permissions apply: the OS user the binary runs as must be a member of the host's docker group.
# Add the user that runs Muximux to the docker group:
sudo usermod -aG docker $(whoami)
# Then re-login (or restart the muximux service) so the new
# group membership takes effect.For systemd unit installs, you can also encode it directly in the unit:
[Service]
User=muximux
SupplementaryGroups=docker
ExecStart=/usr/local/bin/muximux --data /var/lib/muximuxThe default endpoint: unix:///var/run/docker.sock works as is.
Docker Desktop on macOS creates a host-side socket symlink at /var/run/docker.sock, so the default endpoint: unix:///var/run/docker.sock works out of the box for most setups.
If you've disabled Docker Desktop → Settings → Advanced → "Allow the default Docker socket to be used", the symlink isn't created. Either re-enable that toggle, or point endpoint: at the per-user socket Docker Desktop still maintains:
discovery:
docker:
endpoint: unix:///Users/<your-user>/.docker/run/docker.sockDocker on Windows exposes a named pipe rather than a unix socket. Muximux 3.1.0+ supports the npipe:// scheme natively and uses it by default when running on Windows - no extra config needed:
discovery:
docker:
enabled: true
# endpoint is auto-filled to npipe:////./pipe/docker_engine on WindowsIf the Windows account that runs Muximux is a member of the docker-users group, that's sufficient.
For a Docker daemon on a different host:
discovery:
docker:
enabled: true
endpoint: tcp://docker-host.lan:2376
tls:
enabled: true
ca_cert: /etc/muximux/docker-ca.pem
client_cert: /etc/muximux/docker-cert.pem
client_key: /etc/muximux/docker-key.pemThe remote daemon must expose its API with TLS (dockerd --tlsverify), and the cert/key paths must be readable by the Muximux process.
| Strategy | URL shape | Requires |
|---|---|---|
container_ip |
http://<docker-network-ip>:<port> |
Muximux runs in a container on the same docker network, or network_filter is set |
container_dns |
http://<container-name>:<port> |
Same prerequisites as container_ip; Docker's internal DNS resolves names within a network |
host_port |
http://<host_ip>:<published-port> |
Container has -p <host>:<container> published; host_ip is set in config |
host_docker_internal |
http://host.docker.internal:<published-port> |
Muximux runs in a Docker Desktop / WSL container where host.docker.internal resolves |
Strategy gating: when Muximux runs natively (not in a container), container_ip and container_dns need a network_filter to substitute for self-identification. The banner above the form tells you whether the chosen strategy is workable in your environment.
network_filter applies everywhere Muximux reads containers: the scan and the background refresh list the same filtered set and see the same networks. A container attached to several networks gets its URL from the filtered network only, so a refresh never rewrites it to an address on another network. A tracked container that is not on the filtered network counts as missing. On Swarm, the /services endpoint is read once per refresh tick.
Label any container with muximux.* keys and the Discover modal picks them up as high-confidence pre-fills. A fully-labelled container goes from docker compose up to a fully-configured Muximux entry with a single click in the Discover modal, no post-import editing -- or with no click at all if you turn on automatic import. This is the "GitOps your apps" pattern: declare your dashboard intent in docker-compose.yml alongside the service it describes.
Full example, showing one of each kind:
services:
sonarr:
image: linuxserver/sonarr
labels:
# ── Tracking ─────────────────────────────────────────────
- muximux.discovery.id=sonarr-stable
# ── App fields (menu entry) ──────────────────────────────
- muximux.app.enabled=true
- muximux.app.name=Sonarr
- muximux.app.icon=sonarr
- muximux.app.group=Media
- muximux.app.port=8989
- muximux.app.scheme=https
- muximux.app.path=/
- muximux.app.health=/api/v3/health
- muximux.app.color=#3498db
- muximux.app.order=10
- muximux.app.default=false
- muximux.app.open_mode=iframe
- muximux.app.proxy=true
- muximux.app.proxy_skip_tls_verify=true
- muximux.app.min_role=user
- muximux.app.allowed_groups=family,admins
- muximux.app.permissions=clipboard-read,clipboard-write
- muximux.app.allow_notifications=true
- muximux.app.shortcut=1
# ── Gateway routing (subdomain + auth gate) ──────────────
- muximux.app.gateway.domain=sonarr.example.com
- muximux.gateway.tls=auto
- muximux.gateway.streaming=false
- muximux.gateway.strip_frame_blockers=true
- muximux.gateway.forwarded_headers=true
- muximux.gateway.require_auth=true
- muximux.gateway.min_role=user
- muximux.gateway.allowed_groups=family,adminsThe example above sets every label at once. In practice you set only the few an app needs. With automatic import (3.2.0+) these labels are the whole configuration, so here is what each common case looks like on its own. Every block is a complete, copy-pasteable services: entry.
A catalog app, the short way. If the image is one Muximux recognises, a single tracking label is enough -- name, icon, group, port, and health come from the catalog:
services:
sonarr:
image: linuxserver/sonarr
labels:
- muximux.discovery.id=sonarrAn app Muximux does not know. For an image outside the catalog, opt in with enabled and supply the basics:
services:
myapp:
image: ghcr.io/me/myapp
labels:
- muximux.discovery.id=myapp
- muximux.app.enabled=true
- muximux.app.name=My App
- muximux.app.icon=myapp
- muximux.app.group=Tools
- muximux.app.port=8080An app that refuses to embed. Route it through the built-in reverse proxy so it loads in an iframe:
services:
radarr:
image: linuxserver/radarr
labels:
- muximux.discovery.id=radarr
- muximux.app.port=7878
- muximux.app.proxy=truePublished on its own subdomain. Add gateway labels and Muximux serves the app at a public HTTPS address (automatic TLS) behind the login:
services:
grafana:
image: grafana/grafana
labels:
- muximux.discovery.id=grafana
- muximux.app.name=Grafana
- muximux.app.port=3000
- muximux.app.gateway.domain=grafana.example.com
- muximux.gateway.tls=auto
- muximux.gateway.require_auth=trueA button that fires a request, not a page. Use http_action to turn a tile into a webhook trigger (for example an n8n flow), with a confirmation prompt. The app URL (built from port + path) is the request target:
services:
n8n:
image: n8nio/n8n
labels:
- muximux.discovery.id=nightly-backup
- muximux.app.name=Run Backup
- muximux.app.icon=n8n
- muximux.app.port=5678
- muximux.app.path=/webhook/backup
- muximux.app.open_mode=http_action
- muximux.app.http_action_method=POST
- muximux.app.http_action_confirm=trueRestricted to certain people. Gate an app by role and group:
services:
portainer:
image: portainer/portainer-ce
labels:
- muximux.discovery.id=portainer
- muximux.app.port=9000
- muximux.app.proxy=true
- muximux.app.min_role=admin
- muximux.app.allowed_groups=opsYour apps already have public names behind Traefik, Authentik, NPM or another proxy, and Muximux should open those names rather than container IPs:
services:
sonarr:
image: lscr.io/linuxserver/sonarr
labels:
- muximux.app.name=Sonarr
- muximux.app.group=Media
- muximux.app.icon=sonarr
- muximux.app.port=8989
- muximux.app.url=https://sonarr.example.com
- muximux.app.health=/pingSonarr opens at https://sonarr.example.com. The health check calls http://<container IP>:8989/ping, and the poller keeps that address current when the container's IP changes (logging "Docker health address refreshed"); the app URL is never rewritten. A relative health label is resolved against the container; an absolute URL label is kept as written and never rewritten. Without a health label the check goes to the container's URL, including muximux.app.path. Enable monitoring with muximux.app.health_check=true or in the app's settings.
The poller owns the health address of such an app: it rewrites health_url on every refresh, so an edit made in Settings is overwritten. To pin it, set muximux.app.health to an absolute URL, which is kept as written.
Removing muximux.app.url later returns the app to its container URL on the next refresh tick. Editing the URL by hand in Settings detaches the app, as with any tracked app. Known limitation: after removing the muximux.app.url label, an app imported manually keeps the container address it had as its health address; set it in the app's settings if needed. Auto-imported apps get it reset by auto-import.
muximux.app.icon and muximux.group.icon take the same values:
| Value | Icon | Example |
|---|---|---|
<slug> |
Dashboard Icons slug (the original form) | sonarr |
dashboard:<slug> |
Dashboard Icons slug, explicit | dashboard:sonarr |
lucide:<name> |
Lucide icon | lucide:shield-check |
https://... |
Icon image at that URL (https only) | https://example.com/icons/media.svg |
url:<url> |
Icon image at that URL, explicit | url:https://example.com/icons/media.svg |
labels:
- muximux.app.icon=lucide:database
- muximux.group.icon=https://example.com/icons/media.svg- A bare value is always a Dashboard Icons slug. Muximux never guesses Lucide from a bare name, because names such as
serverexist in both sets; writelucide:server. A bare value containing:,/,\,?,#or whitespace is not a slug and is ignored (since 3.6.1); use thedashboard:,lucide:or URL form. - Prefixes are case-insensitive. Slugs and Lucide names are lowercased; URLs are kept exactly as written (paths and queries are case-sensitive).
- A Lucide name is lowercase letters, digits and dashes. Muximux checks that format only, so a name Lucide does not have shows as a missing icon, as in the UI.
- A URL must be an absolute
httpsURL with a host, no credentials or spaces, and at most 2048 characters. Muximux's Content-Security-Policy only allows images over https, sohttpURLs and other schemes such asjavascript:,data:andfile:are refused. The browser loads a URL icon straight from that address (Muximux does not proxy or cache it), so the host must be reachable from the browser. - An invalid value is ignored: the scan notes it, one warning is logged each time it changes, the stored icon is kept, and discovery carries on.
- Custom uploads cannot be named by a label.
A bare muximux.app.icon value is a Dashboard Icons slug -- the icon filename without its extension (sonarr.svg becomes sonarr). Because the label flow has no GUI in front of you, here is where to look one up:
- Browse the gallery at dashboardicons.com and search for your app.
- Or search visually in Muximux's own app editor (Add or Edit an app, then the icon picker); the name you land on is the slug to paste into the label.
- Or list them straight from your running instance:
GET /api/icons/dashboardreturns every available slug (it is the same data the picker uses).
Omit the label to fall back to the catalog icon. For Lucide icons, see Lucide or the Lucide tab of the icon picker.
How values are read:
-
Booleans are true for
true,1,yesoronand false forfalse,0,nooroff(case-insensitive, surrounding spaces ignored). Any other value, including an empty one, is ignored and reported in the Discover modal. This applies to every boolean label (enabled,default,proxy,proxy_skip_tls_verify,allow_notifications,health_check,http_action_confirm,http_action_show_toastand themuximux.gateway.*ones), so a misspeltmuximux.app.enabled=trudoes not opt the container out. A misspelt or empty value keeps the app or gateway site's current setting on update and sync (a typo or an undefined template variable inmuximux.gateway.require_authnever makes a protected site public); on first import the default applies. -
Invalid values for other typed labels (a port outside 1-65535, an unknown
open_mode, a colour that is not a hex colour, and so on) are ignored as if the label were not set. Each ignored value is listed in the Discover modal's notes for the container and logged as a warning once, until it changes. -
Labels that are set win while an app is tracked. On apps you imported by hand,
muximux.app.name,icon,groupandorderare re-synced on every refresh, whatever theauto_importmode. Auto-imported apps are re-synced from all their labels underupdate/syncand left alone underoffandadd. Themuximux.group.*labels apply to groups Docker discovery created. A label that is not set never changes anything. To take a field back, remove the label or Detach the app. See Label re-sync for tracked apps and Group labels. - Unknown
muximux.*labels are listed in the Discover modal's notes for the container.
| Label | Type | Default | What it does |
|---|---|---|---|
muximux.discovery.id |
string | (container name) | Stable tracking key that survives docker-compose --force-recreate and swarm reschedules. Most important label for plain containers - without it, Muximux falls back to the Swarm service, the Compose service, then the container name (see Tracking keys). Swarm tasks and Compose services stay stable without it. |
| Label | Type | Default | What it does |
|---|---|---|---|
muximux.app.enabled |
bool | true |
Opt-out via false. With require_explicit_enable (or MUXIMUX_DISCOVERY_REQUIRE_EXPLICIT_ENABLE) only true opts in. See Explicit opt-in. |
muximux.app.name |
string | catalog name or container name | Display name in the menu. Surrounding spaces are ignored. Re-synced while the app is tracked; see Label re-sync for tracked apps. |
muximux.app.icon |
string | catalog icon or ""
|
A Dashboard Icons slug (e.g. sonarr), lucide:<name>, or an https URL; see Icon label formats. Surrounding spaces are ignored. |
muximux.app.group |
string | catalog group | Group the app lives in. Created if it doesn't exist (from 3.6.0). |
muximux.app.port |
int 1-65535 | catalog port or first exposed | Which container port the app listens on. |
muximux.app.url |
absolute http(s) URL |
unset | Open the app at this URL instead of the container address, e.g. its public name behind your own reverse proxy. Health checks still go to the container. Ignored (with a scan note) when muximux.app.gateway.domain is set, and invalid values are ignored with a note. See Running behind your own reverse proxy. |
muximux.app.scheme |
http | https
|
http |
Scheme for the constructed URL. |
muximux.app.path |
string | / |
Sub-path appended to the container URL (e.g. /admin). Not applied to muximux.app.url or to a gateway site's backend. |
muximux.app.health |
string | catalog default | Health-check address: a full URL, or a path such as /api/v3/health resolved against the container. A value starting with // or /\ is not a path and is ignored (since 3.6.1). |
muximux.app.health_check |
bool | unset | Enables (true) or disables (false) health monitoring for the app; the endpoint still comes from muximux.app.health. Unset keeps the setting in the app's form. Docker-managed: re-synced on update/sync and shown locked in Settings. |
muximux.app.color |
hex colour | unset | Accent color in the dashboard: #rgb, #rgba, #rrggbb or #rrggbbaa. |
muximux.app.order |
int 0-9999 | unset | Sort order within the group. |
muximux.app.default |
bool | false |
Load this app automatically when the dashboard opens. |
muximux.app.open_mode |
iframe | new_tab | new_window | redirect | http_action
|
iframe |
How clicking the menu entry opens the app. http_action fires an HTTP request instead of opening a page. |
muximux.app.http_action_method |
GET | POST | PUT | DELETE | PATCH
|
POST |
With open_mode=http_action, the HTTP method to send to the app URL. |
muximux.app.http_action_headers |
csv Key=Value
|
unset | Extra request headers, comma-separated (e.g. Authorization=Bearer xyz). |
muximux.app.http_action_confirm |
bool | false |
Show a confirmation prompt before firing. |
muximux.app.http_action_show_toast |
bool | true |
Show a result toast after firing. |
muximux.app.proxy |
bool | false |
Route through Muximux's built-in reverse proxy (strips iframe-blocking headers, rewrites paths). Required for many apps that refuse to embed. |
muximux.app.proxy_skip_tls_verify |
bool | true |
When proxy=true, skip backend TLS cert verification (useful for self-signed homelab apps). |
muximux.app.min_role |
user | power-user | admin
|
unset | Minimum role required to see this app. Admins always bypass. |
muximux.app.allowed_groups |
csv | unset | Comma-separated group allow-list; user must belong to at least one. Case-insensitive. Stacks with min_role. |
muximux.app.permissions |
csv | unset | Iframe feature delegations (camera, microphone, geolocation, clipboard-read, clipboard-write, fullscreen, ...). |
muximux.app.allow_notifications |
bool | false |
Enable the cross-iframe Notifications API bridge for this app. |
muximux.app.shortcut |
int 1-9 | unset | Keyboard shortcut slot. |
muximux.app.gateway.domain |
string | unset | Required for auto-import to create a gateway site; the derived <name>.<dashboard domain> default only pre-fills the import modal. When set, the import modal also offers a gateway-site entry for this subdomain. Pairs with the muximux.gateway.* labels below. |
These describe the group the container's app is in. They apply only to a group Docker discovery created; see Group labels.
| Label | Type | Default | What it does |
|---|---|---|---|
muximux.group.icon |
string | folder icon | The same forms as muximux.app.icon (slug, lucide:<name>, https URL). Surrounding spaces are ignored. |
muximux.group.color |
#rrggbb |
unset | Group colour, a hex colour (#rgb, #rgba, #rrggbb or #rrggbbaa). Other values are ignored with a warning and a scan note. |
muximux.group.order |
int 0-9999 | after existing groups | Sort order of the group. Other values are ignored with a warning and a scan note. |
| Label | Type | Default | What it does |
|---|---|---|---|
muximux.gateway.tls |
auto | none | custom
|
auto |
TLS handling for the subdomain. auto runs Let's Encrypt; none is plain HTTP; custom expects tls_cert and tls_key paths (set those manually in the import modal). |
muximux.gateway.streaming |
bool | false |
Disable Caddy response buffering for live-streaming backends. |
muximux.gateway.strip_frame_blockers |
bool | true |
Drop X-Frame-Options / Content-Security-Policy: frame-ancestors on responses so the site can be iframed elsewhere. |
muximux.gateway.forwarded_headers |
bool | true |
Forward X-Forwarded-Proto / X-Forwarded-Host / X-Real-IP. Turn off only when your backend has its own handling. |
muximux.gateway.skip_tls_verify |
bool | false |
Skip verification of the backend's TLS certificate. Only takes effect when the backend URL is https -- use it for self-signed / untrusted backends like Proxmox on :8006. Leaves the public-facing certificate unaffected. |
muximux.gateway.require_auth |
bool | false |
Gate the subdomain behind Muximux's login. Visitors land on /login first; admins bypass per-site role / group rules. Requires server.session_cookie_domain to be set. |
muximux.gateway.min_role |
user | power-user | admin
|
unset | When require_auth=true, minimum role to access the site. |
muximux.gateway.allowed_groups |
csv | unset | When require_auth=true, allow-list groups (comma-separated, case-insensitive). |
Unknown muximux.* labels are surfaced in the Discover modal's per-row notes so a typo is visible, not silently ignored.
Everything above is the manual path: labels become high-confidence pre-fills, and you click Import to commit them. Automatic import removes that review step. When enabled, Muximux imports every container that carries at least one muximux.* label (and is not opted out) by itself -- no modal, no click -- and keeps the imported apps in step with the labels over time.
Security -- read before enabling. Auto-import is off by default and only the host operator (who controls
config.yamlor the environment) can turn it on. Once on, any container on the shared Docker socket can write itself into your config with no review step -- including amuximux.app.gateway.domainlabel that publishes a public HTTPS subdomain with an auto-issued ACME certificate. Treat enabling this as trusting every label on every container the daemon can see. If you don't control all of those containers, leave itoffand import by hand.
Set the mode with the discovery.docker.auto_import config key, or override it with the MUXIMUX_DISCOVERY_AUTO_IMPORT environment variable (the env var wins). Four values:
| Mode | Adds new labelled containers (at least one muximux.* label, not opted out) |
Re-syncs app fields when labels change | Removes the app when the container disappears or opts out |
|---|---|---|---|
off (default) |
no -- labels stay suggestions you import by hand | no | no |
add |
yes, once | no -- imported, then left alone forever | no |
update |
yes | yes | no |
sync |
yes | yes | yes -- full GitOps mirror |
-
off-- today's behavior. Labeled containers are suggestions only; nothing is written until you click Import. -
add-- a newly labeled container is imported one time, then never touched again. Good for bootstrapping. -
update-- likeadd, plus Muximux re-syncs the app's fields from the labels whenever they change. Never removes anything. -
sync-- likeupdate, plus when a tracked container disappears from the daemon, or opts out withmuximux.app.enabled=false, the auto-imported app (and its gateway site) is removed. Removal waits until the container has been absent for three consecutive successful scans, so a restart does not remove and re-create the app. Only auto-imported apps are removed; apps you imported by hand or detached are never touched. The config becomes an exact mirror of the labelled containers.
discovery:
docker:
enabled: true
auto_import: sync # off (default) | add | update | sync# Environment override (takes precedence over config.yaml):
MUXIMUX_DISCOVERY_AUTO_IMPORT=syncThe override applies in memory only and is never written to config.yaml. Settings -> Discovery shows the field as locked ("From MUXIMUX_DISCOVERY_AUTO_IMPORT"). The Discovery tab shows the stored values (host IP, network filter, refresh interval, auto-import mode, badge placement) and its save merges onto the stored config, so fields it does not show are kept. On Windows, npipe:// endpoints can be entered in Settings.
A container with muximux.app.enabled=false is excluded from auto-import and hidden from the Discover modal. Use it to keep a labelled container off the dashboard without stripping its labels. Under sync an app already auto-imported from that container, and its gateway site, is removed after the grace period; under update it is left alone.
By default every labelled container that is not opted out is imported. To require a positive label per container, set discovery.docker.require_explicit_enable: true (or MUXIMUX_DISCOVERY_REQUIRE_EXPLICIT_ENABLE=true, which wins and applies in memory only; Settings shows the field as locked). Then only containers with muximux.app.enabled=true are imported; labelled containers without it are skipped as not_enabled. Invalid values of the environment variable are ignored with a warning. (#496)
A container with no usable URL is never imported. The Discover modal shows a Not importable chip with the reason, and GET /api/discovery/docker/scan returns it as auto_import_skip:
| Code | Meaning |
|---|---|
unlabeled |
No muximux.* label on the container. |
disabled |
muximux.app.enabled=false. |
not_enabled |
Explicit opt-in is on and muximux.app.enabled=true is missing. |
no_port |
No port could be determined; add muximux.app.port. |
no_url |
No URL could be built with the current network strategy. |
invalid |
The labels produce an invalid entry; the detail says why. |
When a container loses all its muximux.* labels, its auto-imported app is detached from auto-import: it is kept, its URL is still refreshed, and sync never removes it. It is still tracked, so the label re-sync applies to it if labels come back. The same happens on upgrade to an auto-imported app whose container has no labels, and when explicit opt-in is turned on for containers without enabled=true.
Auto-import never silently clobbers a URL you took manual control of. Changing an auto-imported app's URL -- in Settings, through the API, or in config.yaml directly -- detaches that app from auto-management. Removing the container's muximux.* labels also detaches it from auto-import (see above). From then on it is a normal manual entry: update/sync will not re-sync it from labels, and sync will not remove it. This is the same edit-lock / auto-detach mechanism described below for tracked URLs.
Other managed-field edits (name, icon, group, and similar) do not detach. The same applies to the health check: when muximux.app.health_check is set, Muximux records a server-owned docker_managed_health_check marker on the app, re-syncs the value under update/sync, and the app form shows the toggle locked. Remove the label to unlock it. Under update/sync they are re-synced from the labels on the next tick, so the labels remain the source of truth; under add they stick, because add never re-syncs an already-imported app.
While an app is tracked, labels that are set win; detach to take control.
This holds for every tracked app, including apps you imported by hand through the Discover dialog, and it works with auto_import: off. On each refresh tick, for every tracked app whose container is present, Muximux applies these labels when they are set on the container:
| Label | Applied as |
|---|---|
muximux.app.name |
The app name. A gateway site linked to the app follows the rename. |
muximux.app.icon |
The icon, as on import (dashboard, Lucide or URL; see Icon label formats). Icon colour, background, variant and invert are kept. |
muximux.app.group |
The group, matched to an existing group by name, ignoring case and spacing (infra finds Infra). Created if no group matches. |
muximux.app.order |
The order within the group. |
The URL and health address keep following the container as before.
- A label that is not set never changes anything: your own name, icon, group or order stays.
- If you change one of these fields in Settings while the label is set, the next tick sets it back. To take control, remove the label or Detach the app (Settings -> Discovery -> Currently tracked). An app you detached (Detach in Settings), or an untracked app you created yourself in Settings, is never touched.
- The re-sync only updates apps that are already tracked. It never imports a container or removes an app, whatever the
auto_importmode. - Auto-imported apps follow their
auto_importmode instead (re-synced underupdate/sync, left alone underoffandadd), so no field is written twice. - If you rename a group in Settings while a container's
muximux.app.grouplabel still names the old group, the label re-creates the old group on the next tick. Change the label too. - A label is held back, and a warning is logged once, when it cannot be applied: the name is already used by another app (names are compared like proxy paths, so
TVandtvcollide), or the name is over 100 characters. The app keeps its current value until the conflict is resolved. - If saving the config fails, the whole tick is rolled back and retried on the next tick.
When an app lands in a group that does not exist, Muximux creates the group in the same save. This covers a manual import from the Discover dialog, auto-import (new apps and re-synced ones) and the label re-sync above, and applies to a group from a muximux.app.group label as well as a catalog group.
- An existing group is matched by name first, then ignoring case and spacing, so
media,Mediaandmedia-server/Media Servernever create a near-duplicate. The app is stored with the existing group's exact name. - A new group gets the same defaults as one added in Settings: a folder icon, no colour, expanded. It is placed after your existing groups, in the order the apps were processed. Customise it in Settings like any other group.
- While a tracked app has
muximux.app.groupset, a group you delete is created again on the next tick. Remove the label or detach the app to stop that. - If the save fails, the new groups are rolled back together with the apps.
- An app whose group is missing anyway (for example a group removed from
config.yamlby hand) is never hidden: the navigation and Settings -> Apps list it under Ungrouped until the group exists again.
muximux.group.icon, muximux.group.color and muximux.group.order set the look and position of the group a container's app is in. Put them on any container whose app is in the group; they need no muximux.app.group on the same container, but without a group there is nothing to apply them to (the Discover dialog notes that).
services:
sonarr:
labels:
- muximux.app.group=Media
- muximux.group.icon=plex
- muximux.group.color=#e5a00d
- muximux.group.order=1-
Only groups Docker discovery created. A group made by an import, auto-import or the label re-sync is marked as Docker-managed (
docker_managed: trueinconfig.yaml). The labels are applied when the group is created and re-synced on every tick while it stays managed. A group you created yourself is never touched. -
Edit in Settings to take control. Changing the icon or colour of a managed group in Settings clears the marker. Changing its order (including dragging groups into a new order) clears it only when the order comes from a
muximux.group.orderlabel; a managed group whose order no label sets can be reordered freely and stays managed. Once the marker is cleared the group is yours and later label changes are ignored. Renaming or expanding/collapsing the group does not count. Setdocker_managed: trueinconfig.yamlto hand a group back to the labels. - A label that is not set never changes anything: the group keeps its own value for that field.
- Conflicts. When several containers in one group set different values, the container with the lowest tracking key wins, per field. The others get a scan note in the Discover dialog and a warning in the log (once until the conflict changes).
- Invalid values (a colour that is not a hex colour, an order outside 0-9999) are ignored, with one warning per change and a scan note.
- The labels are read from the containers of tracked apps and of tracked gateway sites (through the site's linked app), whatever the
auto_importmode. A group created by a manual import gets its label values on the next refresh tick. - Applying them is part of the tick's single save: if the save fails, the group changes are rolled back together with everything else.
In update and sync, re-sync compares both the app fields and the gateway site built from labels against what is stored. Gateway labels that change the app's URL -- muximux.app.gateway.domain and muximux.gateway.tls -- and gateway-only labels that do not map to any app field -- muximux.gateway.require_auth, muximux.gateway.min_role, muximux.gateway.allowed_groups, muximux.gateway.streaming, muximux.gateway.strip_frame_blockers, muximux.gateway.forwarded_headers, and muximux.gateway.skip_tls_verify -- all propagate on the next tick. Toggling muximux.gateway.require_auth on a running container, for example, is re-synced without any app-field change.
Only the explicit muximux.app.gateway.domain label makes auto-import create a gateway site. With server.tls.domain set, the derived <name>.<dashboard domain> default is only a pre-fill in the import dialog. An update changes only label-managed fields: health check, auth bypass, access, scale, pinned, proxy headers and other per-app settings are kept.
Upgrade note: earlier versions created a gateway site <name>.<your domain> for every labelled container when server.tls.domain was set. If you relied on those derived subdomains, add muximux.app.gateway.domain=<name>.<your domain> to those containers before upgrading; otherwise the first refresh removes the sites and points the apps at their container URLs.
Removing the muximux.app.gateway.domain label from an already-imported container reverts its app to the direct container URL and drops the now-orphaned gateway site on the next tick.
Swarm services and Compose projects are tracked by name, so redeploys and --force-recreate do not create duplicates.
The key is the first of these that applies:
| Order | Source | Key |
|---|---|---|
| 1 |
muximux.discovery.id label |
label:<value> |
| 2 | Swarm service | swarm:<service> |
| 3 | Compose project and service | compose:<project>:<service> |
| 4 | Container name |
name:<name> (shown with a stability warning) |
| 5 | Container ID |
id:<id> (last resort) |
Swarm tasks and Compose services therefore no longer need muximux.discovery.id for stability. Existing name: keys are migrated to the stable key automatically on the first refresh (a Docker tracking key migrated audit line is logged). Entries tracked on another endpoint are migrated after Re-link. Quarantined entries keep their old key; sync removes them once the task is gone from the endpoint it polls, or you can remove them in Settings. Duplicates left by earlier redeploys are cleaned by sync.
There is one app per key: Swarm replicas and the containers of a scaled Compose service (--scale) are collapsed into a single app. With container_ip and several replicas the address may alternate between them.
- Labels under the service's
labels:reach the task.deploy.labelsare service labels and are read too; container labels win when both set the same key. - Published ports come from the service, so the
host_portandhost_docker_internalstrategies work with ingress-published ports. -
container_dnsuses the service name. - The endpoint must be a manager node, because only managers answer
/services. On a worker the scan shows a note and the app needsmuximux.app.port. - Behind a socket proxy such as tecnativa/docker-socket-proxy, allow
/serviceswithSERVICES=1. Without it Muximux logs one warning ("Swarm services unavailable; using task data only") and uses task data only, so service labels and published ports are missing.
An invalid Docker-owned entry in config.yaml (an auto-imported app or gateway site that fails validation, for example one written without a URL by an older version) no longer stops Muximux from starting and no longer rejects a save from Settings. Instead it is:
- kept in
config.yaml, unchanged, until it is fixed, removed, or superseded (see below), - not loaded, so it does not appear on the dashboard,
- logged once at startup as "Invalid Docker auto-imported entry quarantined", and
- listed in Settings -> Discovery -> Tracked with its reason.
Fix the container's labels and the next refresh replaces the entry, or remove it from the list (Remove or Remove all). Removing deletes the entry from config.yaml; a live app or gateway site that shares its tracking key stays tracked. A quarantined entry is also superseded, and dropped from config.yaml at the next save (logged as "Quarantined entry superseded by a live entry"), when a live app takes its name or slug or a live gateway site takes its domain; a quarantined site goes with its superseded app. Under sync, a quarantined app whose container is gone from the polled endpoint is removed after three consecutive scans, like an auto-imported app. A manual gateway site that was linked to a quarantined app loses its app link. Only Docker-owned entries are quarantined; other invalid config still fails validation as before.
Other housekeeping that applies to discovery: image names whose last segment is generic (server, app, web, api and similar) no longer match a catalog entry, apps whose names differ only by case or spacing are deduplicated, and a tracked container that cannot be found is logged once when it goes missing instead of on every refresh.
Discovery behaves more predictably from 3.6.0, and a few changes show up on the first refresh after an upgrade:
-
Auto-import needs a label. Only containers with at least one
muximux.*label are auto-imported. An auto-imported app whose container has no labels is detached from auto-import on the first refresh underupdate/sync: the app stays and its URL keeps refreshing, butsyncno longer removes it. Add a label (muximux.discovery.idis enough) if you want the container managed again. See Containers that lose their labels. -
New tracking keys for Swarm and Compose. Keys become
swarm:<service>andcompose:<project>:<service>and existingname:keys are migrated in place on the first refresh (logged asDocker tracking key migrated). Entries tracked on another endpoint are migrated after Re-link. See Tracking keys. -
Swarm app names. Under
update/sync, an auto-imported Swarm app withoutmuximux.app.nameis renamed from the task name to the service name, which can change its slug and its/proxy/<slug>/path. Behind a socket proxy, allow/services(SERVICES=1). -
Labels re-sync onto apps you imported by hand. A set
muximux.app.name,icon,groupororderlabel now wins on every tracked app, in every mode includingoff, so an edit to those fields in Settings is reset on the next refresh. Remove the label or Detach the app to keep your edit. See Label re-sync for tracked apps. -
Invalid entries no longer stop startup. An invalid auto-imported app or gateway site in
config.yamlis quarantined (kept in the file, not loaded) with a warning. See Quarantined entries. -
syncremoves less. Only auto-imported apps are removed, and only after the container has been absent for three consecutive scans. -
Catalog matching. Images whose last path segment is generic (
server,app,web,apiand similar) no longer match a catalog entry by that segment, so such a container may needmuximux.app.name,iconandportlabels. -
lifecycle_allowed_groupsholds user and identity-provider group names; renaming or deleting a dashboard group no longer changes it.
From 3.6.1:
- The Docker whale in the navigation and on the overview is shown to admins only, and can be hidden with
navigation.show_docker_badge: false. -
MUXIMUX_DISCOVERY_AUTO_IMPORTandMUXIMUX_DISCOVERY_REQUIRE_EXPLICIT_ENABLEnow also apply on a first start withoutconfig.yaml. A container that relied on them being ignored there will see them take effect. - A
muximux.app.healthvalue starting with//or/\is ignored. - A bare
muximux.app.icon(ormuximux.group.icon) value is trimmed, then refused as a Dashboard Icons slug if it still contains:,/,\,?,#, whitespace or control characters; use thedashboard:,lucide:or URL forms (see Icon label formats).
When you check "Add to menu" in the import modal, a radio selector decides how the menu link works:
Browser → http://192.168.1.4:8989 (your dashboard machine reaches the container directly)
- App.url = the discovered container URL
- App.proxy = false
- The poller refreshes App.url every tick when the container's IP changes
Use when: your dashboard machine has direct network reachability to the container (same host, same subnet, VPN).
Browser → /proxy/sonarr → Muximux Go server → http://192.168.1.4:8989
- App.url = the container URL (kept as the upstream)
- App.proxy = true
- Muximux's built-in path-prefix reverse proxy strips iframe-blockers, rewrites HTML/CSS/JS paths, isolates
window.parent, etc. - makes apps work in iframes that refuse them. - Same tracking semantics as Direct (poller refreshes the upstream URL)
Use when: the app misbehaves in iframes, the dashboard machine cannot reach the container directly, or you want auth/CSP layering through Muximux.
Browser → https://sonarr.example.com → Muximux (embedded Caddy, :443, auto-HTTPS) → http://192.168.1.4:8989
Point the subdomain's DNS at the Muximux host: Muximux is the reverse proxy here. Its embedded Caddy binds 80/443, provisions the certificate via Let's Encrypt, and proxies the request to the container. You do not put nginx or another proxy in front of it for this to work.
-
App.url =
https://<gateway-domain>(static) - App.proxy = false
- App is NOT auto-managed - the gateway site becomes the docker-managed entry instead
- The poller refreshes the gateway site's
backend_url(not the App's URL, which doesn't depend on the container)
Use when: you want a public subdomain that survives container moves, and a clickable dashboard link that uses that domain.
If you already run an edge proxy (Cloudflare Tunnel, an upstream Traefik/nginx) and would rather it terminate TLS, set
server.gateway_listen: ":8443"and have that proxy forward to Muximux on that port instead. In that case the flow isBrowser → upstream proxy → muximux:8443 → http://192.168.1.4:8989.
When an App or GatewaySite is tracked, the URL field is read-only in the editor with an amber lock badge. Muximux protects your edits across all three ways you might change the URL:
- In Settings, the URL field is locked. To change it, click Detach under Settings → Discovery → Currently tracked. The app's URL unlocks right away in the same Settings session; saving afterwards keeps it detached.
- Through the API, if a SaveConfig request changes the URL of a tracked entry, Muximux treats that as a deliberate takeover, drops the tracking, and writes an audit log entry.
-
In
config.yamlby hand, the same thing happens at next boot: Muximux notices your URL differs from the one the poller last wrote, drops the tracking, and notes it in the log. Your edit survives the next refresh tick.
The sanctioned forget path is the Detach button in Settings → Discovery (or DELETE /api/discovery/docker/track/<key> from a script).
Tracking itself belongs to Muximux, not to the save payload: docker_key, docker_endpoint, docker_strategy and docker_managed_url in a SaveConfig or per-app PUT are ignored and the stored values kept. A save can detach an app by changing its URL, but it cannot attach one, re-attach a detached one, or switch it to another container. Only Discover, auto-import and Re-link create or change tracking.
Apps with a fixed muximux.app.url are not rewritten by the poller; their docker_managed_url equals the label URL. Their health_url is refreshed from the container on every tick, so a health address edited in Settings is overwritten unless muximux.app.health is an absolute URL.
You'll see a docker_managed_url field appear next to docker_key for tracked entries. Muximux writes it from the import flow and updates it on every poller tick. You don't need to touch it. If you do hand-author a tracked entry from scratch, just set docker_managed_url to the same value as url (or backend_url for a gateway site) so the file-edit detach mechanism has a baseline to compare against.
Caddy's reload is transactional only at the parse step. Post-parse failures (listener collision, async cert provisioning, module panic) can leave the running config in an indeterminate state. The refresh poller handles this with rollback:
- Candidate reload fails, rollback reload succeeds → audit log warning, refresh tick skipped
- Both fail → divergence counter increments, sticky red banner appears in Settings → Discovery
- First clean tick after a divergence → banner transitions to amber "recovered"
The banner gives you a one-glance signal that the running Caddy may not match disk. Recovery happens automatically on the next successful tick; the banner stays amber until you acknowledge it (currently by waiting - a future iteration may add a "clear" button).
Caddy binds 80/443 with auto-HTTPS by default, which requires root or CAP_NET_BIND_SERVICE. For "behind another proxy" topologies (Traefik, Cloudflare Tunnel, nginx), set server.gateway_listen to a non-privileged port:
server:
listen: ":8080"
gateway_listen: ":8443" # gateway sites served as plain HTTP hereSee TLS and Gateway → Running Behind Another Reverse Proxy for the full topology guide.
To have the dashboard open your proxy's public names instead of container addresses, see Running behind your own reverse proxy.
| Symptom | Diagnosis |
|---|---|
Banner: "Connected to Docker but strategy container_ip cannot identify Muximux's container" |
Muximux runs natively (not in a container). Set network_filter to scope the scan to a known docker network, or switch to host_port. |
| Banner: "Daemon unreachable: dial unix … no such file or directory" | The endpoint path is wrong, or the socket isn't bind-mounted into Muximux's container. |
| Banner: "Daemon unreachable: connect: permission denied" | The socket is mounted but the entrypoint's auto-detection didn't fire (e.g. unusual mount path, docker-socket-proxy sidecar). Override with DOCKER_GID set to the docker group GID the socket is owned by, or DOCKER_SOCKET to point the detection at a non-default path. See Make the daemon socket reachable. |
| Discover modal shows containers but no auto-fill | The image isn't in Muximux's catalog. Add muximux.app.* labels to the container, or fill the fields manually before importing. |
| Imported app's URL doesn't update when container restarts | Check refresh_interval isn't set to 1h. Check the audit log for Docker app URL refreshed (and Docker health address refreshed for health addresses). Check the container hasn't been renamed (breaks name: tracking keys; Swarm and Compose containers use stable keys). |
| Gateway site doesn't serve after import | If you set server.gateway_listen, your upstream proxy needs to forward the host header to that port. Try curl -H 'Host: site.example.com' http://muximux-host:8443/ to bypass the upstream. |
| Discover modal shows "Not importable: No port" | The container exposes no port Muximux can pick. Add muximux.app.port. |
| Container is labelled but not auto-imported | Check the reason in the Discover modal. Common causes: muximux.app.enabled=false, or explicit opt-in is on and muximux.app.enabled=true is missing. Containers with no muximux.* label are never auto-imported. |
| Log says "Invalid Docker auto-imported entry quarantined" | The entry is kept in config.yaml but not loaded. Fix the container's labels (the next refresh replaces it) or remove it under Settings -> Discovery -> Tracked. A manual gateway site linked to it loses its app link. |
| Log says "Swarm services unavailable; using task data only" | The endpoint is not a manager node, or a socket proxy blocks /services. Point Muximux at a manager, or set SERVICES=1 on the proxy. |
| Divergence banner is red and won't clear | Inspect the most recent Docker refresh divergence audit log line for the candidate + rollback errors. Most often a Caddyfile parse-OK but listener-collide situation. Restart Muximux to recover. |
All endpoints are admin-only except GET /api/discovery/docker-state, which returns the container state of the apps the caller can see.
| Method | Path | Body | Description |
|---|---|---|---|
| GET | /api/discovery/docker/status |
- | Capability + cache status |
| GET | /api/discovery/docker/networks |
- | Docker networks, for the network filter |
| GET | /api/discovery/docker/config |
- | The stored discovery settings under config, plus env_overrides for fields set by an environment variable |
| PUT | /api/discovery/docker/config |
DiscoveryDockerConfig |
Merge the fields sent onto the stored discovery settings (omitted fields are kept), save, and rebuild the service |
| POST | /api/discovery/docker/test |
DiscoveryDockerConfig |
Probe a candidate config without saving |
| GET | /api/discovery/docker/scan |
- | Enumerate running containers as Suggestion list, with labeled, label_enabled, auto_import_skip, tracked and name_taken
|
| POST | /api/discovery/docker/import |
{items: ImportItem[]} |
Atomic batch import of selected containers |
| GET | /api/discovery/docker/tracked |
- | Current tracked apps + sites with last-seen and missing_since timestamps, and the quarantined entries |
| DELETE | /api/discovery/docker/track/{key} |
- | Detach tracking for everything matching key on the current endpoint, and delete quarantined entries with that key on any endpoint. ?scope=quarantined deletes only the quarantined entries |
| POST | /api/discovery/docker/relink/probe |
{key} |
"Does this key still resolve on the current daemon?" |
| POST | /api/discovery/docker/relink/confirm |
{old_key, new_key, strategy?} |
Move tracking from old key to new key |
| GET | /api/discovery/docker-state |
- | Container state of tracked apps (any signed-in user; filtered to the apps they can see) |
Field-level details and examples are in the API Reference.
If you want the dashboard to start / stop / restart tracked containers from the overview page:
- Edit
docker-compose.ymland switch the Docker socket mount from:roto:rw-- change the line to- /var/run/docker.sock:/var/run/docker.sock:rw. The:romount stays as the documented default; you opt in by changing this one character. - Set
discovery.docker.lifecycle_enabled: truein yourconfig.yaml, or toggle "Enable container lifecycle controls" under Settings -> Discovery (the checkbox is disabled until the socket is writable). - Optional: narrow who can use the controls.
discovery.docker.lifecycle_min_roledefaults toadmin; set it topower-useroruserto widen access. Setdiscovery.docker.lifecycle_allowed_groupsto additionally require membership in specific groups. These are user and identity-provider group names (built-in user groups, the OIDC groups claim, the forward-auth groups header), not dashboard groups. Renaming or deleting a dashboard group never touches this list. - Optional: set
discovery.docker.health_badge_placementtooverview_and_navto show container state badges in the navigation sidebar as well as the overview (default isoverview;offhides them).
Once enabled, Docker-tracked apps on the overview show the Docker logo (to admins only; hide it with navigation.show_docker_badge: false, see Navigation > Docker Badge) plus a small status dot when the container needs a glance: red for stopped, amber for unhealthy or paused, blue for restarting (a healthy, running container shows no dot - quiet by default). Hovering the card reveals the action buttons in a footer below it - Start when stopped, Stop and Restart when running; on touch devices the buttons stay visible. The footer sits outside the card's open-app area, so a tap to open the app can't trigger a container action by accident. Stop and Restart prompt for confirmation; Start fires immediately.
Every action - success, failure, and denied attempt (role floor not met, group mismatch, socket read-only, lifecycle disabled) - is audit-logged with the caller's username, app name, container id, and outcome (source=audit).