Browse Source

zwave-js-ui: name the image in the container, drop the .image unit

A .image unit runs `podman image pull`, which contacts the registry even when
the image is already in local storage. A machine that boots before its network
does fails that pull with "Temporary failure in name resolution", and the
Requires= quadlet generates on the .image unit takes the container down with it.
A failed dependency is never retried -- Restart=always only applies to a unit
that started at least once -- so the service stays down until someone notices.

Observed on the target: the radio head was moved to a basement, powered on, and
cabled some minutes later. It booted, NetworkManager-wait-online declared the
network online 26 s in, the pull failed on DNS, and zwave-js-ui never started
even though the image was sitting in local storage and the controller was
plugged in.

Naming the image in the container lets podman resolve it at start with its
default --pull=missing: the local image starts with no registry involved, and a
genuinely missing image fails the container, which Restart= then retries until
the network is there. Auto-update is unaffected, it reads the container's label.

This is the one deviation from the convention of this repository, and the README
says so and why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pull/11/head
Nicolas Massé 1 month ago
parent
commit
dce163b263
  1. 14
      cookbooks/zwave-js-ui/README.md
  2. 16
      cookbooks/zwave-js-ui/zwave-js-ui.container
  3. 12
      cookbooks/zwave-js-ui/zwave-js-ui.image

14
cookbooks/zwave-js-ui/README.md

@ -27,7 +27,8 @@ This cookbook:
- Supports automatic container image updates via Podman auto-update.
The image is `ghcr.io/zwave-js/zwave-js-ui:11`, a multi-architecture manifest (amd64 +
arm64, verified 2026-09-26, version 11.24.1).
arm64, verified 2026-09-26, version 11.24.1). It is named in the container unit rather than in
a `.image` unit — see the deviations below, and the reason is boot resilience, not taste.
## What this cookbook does NOT do
@ -198,6 +199,17 @@ a restart loop fixes.
artifact. What the external file buys is not secrecy: it is that the authoritative copy lives
outside the application, is restorable from a vault, and cannot be edited from the web
interface.
- **No `.image` unit: the image is named in the container.** A `.image` unit runs
`podman image pull`, which contacts the registry even when the image is already in local
storage. A machine that boots before its network does — a power cut, a switch slower than the
machine, a cable plugged in after power-on — fails that pull with
`Temporary failure in name resolution`, and the `Requires=` that quadlet generates on the
`.image` unit takes the container down with it. **A failed dependency is never retried**:
`Restart=always` only ever applies to a unit that started at least once, so the service stays
down until someone notices. Naming the image in the container instead lets podman resolve it
at start with its default `--pull=missing`: the local image starts with no registry involved,
and a genuinely missing image fails the container, which `Restart=` then retries until the
network is there. Auto-update is unaffected — it reads the container's label.
- **Only `external-settings.json` is mounted into the container, never the configuration
directory.** `:Z` relabels what it mounts to `container_file_t`, recursively and on disk, and
`init.sh` lives in that directory. Mounting the directory therefore leaves a script systemd

16
cookbooks/zwave-js-ui/zwave-js-ui.container

@ -21,7 +21,21 @@ PartOf=zwave-js-ui.target
[Container]
ContainerName=zwave-js-ui
Image=zwave-js-ui.image
# The image reference, and NOT a zwave-js-ui.image unit -- the one deviation from
# the convention of this repository, for a reason this cookbook has already paid
# for. A .image unit runs `podman image pull`, which contacts the registry even
# when the image is already in local storage. A radio head that boots before its
# network does -- a power cut, a switch that is slower than it is, a cable
# plugged in after the machine -- therefore fails that pull, and the generated
# `Requires=` on it takes the container down with it. A failed dependency is
# never retried: Restart=always only ever applies to a unit that started once.
#
# Named here instead, podman resolves the image at container start with its
# default --pull=missing: the local image starts with no registry involved, and a
# genuinely missing image fails the container, which Restart= below then retries
# until the network is there. Auto-update still works -- it reads the label of
# the container, not the .image unit.
Image=ghcr.io/zwave-js/zwave-js-ui:11
AutoUpdate=registry
# No need for root privileges: the image has no entrypoint script, it execs node

12
cookbooks/zwave-js-ui/zwave-js-ui.image

@ -1,12 +0,0 @@
[Unit]
Description=podman pull ghcr.io/zwave-js/zwave-js-ui
Documentation=https://zwave-js.github.io/zwave-js-ui/
[Image]
# Multi-architecture manifest (amd64 + arm64, verified 2026-09-26 on tag 11 ->
# 11.24.1), so the cookbook installs on an aarch64 host as well as on the x86_64
# radio head. The tag is the major version: Z-Wave JS UI ships the zwave-js
# driver and its Z-Wave JS Server, whose schema version is what Home Assistant
# negotiates. A major bump is the one that can break that handshake, so it is
# the one an operator has to opt into.
Image=ghcr.io/zwave-js/zwave-js-ui:11
Loading…
Cancel
Save