docker-compose.balena.dev.yml.tmpl
Rama master · réplica de lectura del repositorio original.
# vim: ft=yaml
version: "2"
services:
anthias-server:
image: ghcr.io/screenly/anthias-server:${GIT_SHORT_HASH}-${BOARD}
# A crash must not cost the device its storage. The host's
# core_pattern is the kernel default `core`, a relative path, so a
# dump lands in the crashing process's working directory, which is
# WORKDIR /usr/src/app: inside the container, on no mount. With
# core_uses_pid=1 every crash adds a fresh `core.<pid>` rather than
# replacing one, and nothing sweeps them. They are invisible to
# `docker images`, and `docker image prune` cannot touch a live
# container's writable layer, so the usual disk investigation comes
# back clean while the card fills. A device in the field reached
# 24.2 GB of them in anthias-celery (issue 3417). Sentry already
# reports crashes, so on a device a dump buys nothing and costs the
# card. Anchored here and aliased by every service below: the rule
# is "any of our containers", not "the one that happened to crash".
ulimits: &no-core-dumps
core: 0
build:
context: .
dockerfile: ./docker/Dockerfile.server
ports:
- 80:8080
environment:
- HOME=/data
- LISTEN=0.0.0.0
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
depends_on:
- redis
# No `devices:` here, and none needed. HDMI-CEC is driven by the
# anthias-viewer container, which is privileged and therefore
# already sees every /dev/cec* node; this service asks it over the
# Redis command bus (src/anthias_server/lib/cec_client.py).
#
# That indirection is what makes CEC work on balena at all. The
# compose file here is baked into the release from a workstation,
# nothing on-device can enumerate the host's adapters, and a
# statically listed node that turns out to be absent would stop the
# container from starting. /dev/vchiq used to be listed for libcec,
# which is gone.
restart: always
volumes:
- resin-data:/data
labels:
io.balena.features.supervisor-api: '1'
anthias-viewer:
image: ghcr.io/screenly/anthias-viewer:${GIT_SHORT_HASH}-${BOARD}
ulimits: *no-core-dumps
build:
context: .
dockerfile: ./docker/Dockerfile.viewer
depends_on:
- anthias-server
# Issue #480 — see docker-compose.balena.yml.tmpl. balena Device or
# Service Variables (e.g. LANG=nl_NL.UTF-8) are injected by the
# supervisor at runtime; no compose entry needed for AnthiasWebview
# to pick them up.
environment:
- HOME=/data
- PORT=8080
- NOREFRESH=1
- LISTEN=anthias-server
# See docker-compose.balena.yml.tmpl: lets start_viewer.sh quit the
# Plymouth boot splash over the host system bus (mounted by
# io.balena.features.dbus below) so it releases DRM master before
# cage starts. See release_boot_splash() in bin/start_viewer.sh.
- DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
privileged: true
restart: always
shm_size: ${SHM_SIZE}
volumes:
- resin-data:/data
labels:
io.balena.features.supervisor-api: '1'
io.balena.features.dbus: '1'
anthias-celery:
# Runs on the same image as anthias-server with a CMD override.
# See docker-compose.yml.tmpl for context on the merge.
image: ghcr.io/screenly/anthias-server:${GIT_SHORT_HASH}-${BOARD}
ulimits: *no-core-dumps
command: >
nice -n 19 ionice -c 3
celery -A anthias_server.celery_tasks.celery worker -B -n worker@anthias
--loglevel=info --schedule /tmp/celerybeat-schedule
depends_on:
- anthias-server
- redis
environment:
- HOME=/data
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
# No `devices:` here, and none needed. HDMI-CEC is driven by the
# anthias-viewer container, which is privileged and therefore
# already sees every /dev/cec* node; this service asks it over the
# Redis command bus (src/anthias_server/lib/cec_client.py).
#
# That indirection is what makes CEC work on balena at all. The
# compose file here is baked into the release from a workstation,
# nothing on-device can enumerate the host's adapters, and a
# statically listed node that turns out to be absent would stop the
# container from starting. /dev/vchiq used to be listed for libcec,
# which is gone.
restart: always
volumes:
- resin-data:/data
labels:
io.balena.features.supervisor-api: '1'
# The WiFi setup portal. Started by the supervisor on every boot and
# every release apply, which is safe because the DAEMON decides whether
# a portal is needed -- there is no systemd here to hang an
# ExecCondition on, and nothing in a compose file can gate a service.
#
# wifi-connect 2026.10.11 is the release that makes that true: before
# it, a device online over ethernet alone was not considered online
# (NetworkManager reports connectivity as "unknown" wherever the check
# is unavailable, and only an associated radio counted), so every start
# raised an access point at a perfectly healthy screen. Verified on a
# wired Pi 5 and Pi 3 B+ against 2026.10.11: exits 0 in ~20 seconds
# with "already online; the portal is not needed", no access point, no
# state file. The same test against 2026.10.10 raises one.
#
# Do NOT pin this below 2026.10.11. 2026.10.12 matters too:
# missing branding is a warning there rather than a refusal to
# start, which is what makes staging it on the shared volume
# safe, and a guessed `limited` verdict no longer raises an
# access point at a wired screen on an isolated LAN.
#
# Measured on balenaOS 2.83.18 (NetworkManager 1.28.0) booted in QEMU,
# because the three facts this service depends on there are not
# obvious from the Debian path:
#
# * ConnectivityCheckAvailable is FALSE, exactly as on Debian and
# Raspberry Pi OS -- so the "unknown" fallback above is the code
# path that runs here too. (meta-balena's current bbappend does
# add `concheck` to PACKAGECONFIG; this released build predates
# that, and either way the daemon handles both.) `Connectivity`
# itself still answers 4/FULL, which is the trap: it is optimistic
# when no probe ran.
#
# * supervisor0 is a BRIDGE that NetworkManager reports as
# `connected (externally)`. Upstream's online check counts only
# ethernet, wifi and modem devices, which is what stops every
# balena device reading as permanently online and never raising
# a portal at all.
#
# * The host D-Bus socket reaches the container through the label
# below: the pinned image run with exactly this env and mount
# exits 3 ("no WiFi device was found" -- the VM has no radio),
# not 5, which is what a failure to reach NetworkManager looks
# like.
anthias-wifi-connect:
image: ghcr.io/screenly-labs/wifi-connect:2026.10.13
ulimits: *no-core-dumps
# Serving DHCP, which does not survive Docker's NAT, and binding the
# access point's own address on the wireless device.
network_mode: host
# `always` is the schedule here, because there is nothing else that
# can be one.
#
# `restart: "no"` does NOT mean "start once per boot", on balena or
# anywhere else. It means nothing will start the container again
# once it has exited, and that is by design at both layers:
#
# * The supervisor starts a service only if it has never been
# started. Its own words, in compose/app.ts: "Only start a
# Service if we have never started it before and the service
# matches target! This is so the engine can handle the restart
# policy configured for the container." An exited service is
# neither Installing nor Installed, so it is left alone. The one
# exception is a container the Engine failed to start because a
# host interface was not ready, matched by its error message.
#
# * balena-engine, like Docker, only starts containers at daemon
# start if their policy is `always` or `unless-stopped`.
#
# So on Debian `restart: "no"` is right because systemd starts the
# container; here there is no systemd, and nothing else volunteers.
# Confirmed on balenaOS 9.0.3 with supervisor 18.2.2: the
# container's start and finish times were unchanged across a reboot.
#
# Reboot-only would not be enough anyway. The common failure is a
# screen that stays powered on while the WiFi password changes
# around it, and telling an operator to power-cycle a wall-mounted
# display is the advice this feature exists to replace.
#
# What makes `always` affordable is that the daemon waits before
# exiting (below). Without that it is a spin: measured on a wired
# Pi 5, a container start every eighteen seconds, 4,680 a day, with
# 19,000 log lines to match -- which on balena ship to the cloud.
restart: always
environment:
# NetworkManager lives on the host; io.balena.features.dbus below
# is what puts its socket in this container.
- DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
# A PREFIX: the portal appends four random characters, so five
# screens in one room do not all broadcast one name.
- PORTAL_SSID=Anthias Setup
- WIFI_CONNECT_BRAND_NAME=Anthias
# Staged on the shared volume by anthias-server's startup
# script, because ansible does not run here. Missing files
# are a warning upstream as of 2026.10.12, not a refusal to
# start, so losing the race on a first boot costs the portal
# its colours and not its existence.
- WIFI_CONNECT_BRAND_CSS=/data/hotspot/brand/brand.css
- WIFI_CONNECT_BRAND_LOGO=/data/hotspot/brand/logo.svg
- WIFI_CONNECT_DEFAULT_THEME=dark
# NOT 80: anthias-server publishes 80:8080 on the host.
- PORTAL_LISTENING_PORT=8000
- WIFI_CONNECT_QR_OUTPUT=/data/hotspot
# How anthias-server learns the SSID, passphrase and portal
# address to put on the screen, and whether a portal is live.
- WIFI_CONNECT_STATE_FILE=/data/hotspot/state.json
- WIFI_CONNECT_NO_QR_STDOUT=true
# Idle, not a session cap, as of 2026.10.9.
- ACTIVITY_TIMEOUT=900
# The cadence `restart: always` turns into a schedule, as of
# 2026.10.13. Five minutes between checks on a screen that is
# online -- the check is one D-Bus round trip -- and six hours
# after a setup page nobody used, because that one just held the
# display for a quarter of an hour. A site with nothing to join
# therefore spends a minute in twenty-four showing setup instead
# of content, which is the same budget the systemd timer and its
# backoff give the Debian path.
- WIFI_CONNECT_RECHECK_INTERVAL=300
- WIFI_CONNECT_RETRY_AFTER=21600
volumes:
- resin-data:/data
labels:
io.balena.features.dbus: '1'
redis:
image: ghcr.io/screenly/anthias-redis:${GIT_SHORT_HASH}-${BOARD}
ulimits: *no-core-dumps
build:
context: .
dockerfile: ./docker/Dockerfile.redis
ports:
- 127.0.0.1:6379:6379
restart: always
volumes:
- redis-data:/var/lib/redis
volumes:
resin-data:
redis-data: