A reusable balena block that provides a hardware-accelerated Weston Wayland compositor, enabling any containerised app to render graphics on embedded Linux devices.
- Raspberry Pi 4
- Raspberry Pi 5
- Generic x86_64 (GPT)
Pre-built images are published to the balena registry for each supported architecture:
| Image | Architecture | Devices |
|---|---|---|
bh.cr/balenasolutions/display-aarch64 |
ARM 64-bit (aarch64) |
Raspberry Pi 4, Raspberry Pi 5 |
bh.cr/balenasolutions/display-amd64 |
x86-64 (amd64) |
Generic x86_64 (GPT) |
Reference the architecture-specific image directly. Best when your fleet targets a single known architecture (e.g. aarch64 for Raspberry Pi 4/5):
docker-compose.yml
version: '2.1'
services:
display:
image: bh.cr/balenasolutions/display-aarch64
privileged: true
restart: always
network_mode: host
volumes:
- display-socket:/run
labels:
io.balena.features.dbus: '1'
your-app:
build: ./your-app
restart: always
depends_on:
- display
volumes:
- display-socket:/run
devices:
- /dev/dri:/dev/dri
environment:
- WAYLAND_DISPLAY=wayland-0
- XDG_RUNTIME_DIR=/run/user/0
volumes:
display-socket:Use a Dockerfile.template so balena substitutes the correct architecture at build time. Best for when you deploy your app to fleets of different architectures (e.g aarch64 and amd64):
./display/Dockerfile.template
FROM bh.cr/balenasolutions/display-%%BALENA_ARCH%%docker-compose.yml
version: '2.1'
services:
display:
build: ./display
privileged: true
restart: always
network_mode: host
volumes:
- display-socket:/run
labels:
io.balena.features.dbus: '1'
your-app:
build: ./your-app
restart: always
depends_on:
- display
volumes:
- display-socket:/run
devices:
- /dev/dri:/dev/dri
environment:
- WAYLAND_DISPLAY=wayland-0
- XDG_RUNTIME_DIR=/run/user/0
volumes:
display-socket:In your app's entry script, wait for the socket before launching:
SOCKET="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"
while [ ! -e "$SOCKET" ]; do sleep 1; done
exec your-appYou can configure Weston sections and keys using the following environment variables:
| Variable | Options | Default | Description |
|---|---|---|---|
XDG_RUNTIME_DIR |
valid directory path | /run/user/0 |
Directory where the Wayland socket is created. |
SOCKET_NAME |
[String] (e.g., wayland-0, wayland-1) |
wayland-0 |
Name of the Wayland socket |
WESTON_DEBUG |
true, false | false |
Enable debug mode |
WESTON_INI_PATH |
Absolute path to a custom weston.ini file |
- | When this variable is set and the file is present, Weston will be launched directly using the provided file. Consequently any other DISPLAY_* environment variables defined for the container will be ignored. |
DISPLAY_UX_MODE |
desktop, kiosk |
kiosk |
Sets the user interface mode. Use kiosk for a single full-screen application(ideal for embedded use cases), or desktop for a multi-window environment. |
DISPLAY_IDLE_TIMEOUT |
[Integer] (seconds) | 0 |
Time in seconds before the display enters an inactive mode and blanks the screen. 0 disables the idle timeout. Note: Only applicable when DISPLAY_UX_MODE=desktop. |
DISPLAY_REQUIRE_INPUT |
true, false |
false |
Dictates whether an active input device is required to launch. false permits display-only deployments. |
DISPLAY_ALLOW_LOCKING |
true, false |
false |
Enables or disables screen locking functionality. |
DISPLAY_PANEL_POSITION |
top, bottom, left, right, none |
none |
Sets the location of the desktop panel. none disables the panel entirely, ensuring an unobstructed viewport. |
DISPLAY_ROTATION |
0, 90, 180, 270 |
0 |
Rotates the displayed image clockwise by this many degrees (e.g. 90 turns the picture 90° clockwise; its top edge moves to the right). Applied to the first connected display (see Display geometry & rotation). |
DISPLAY_RESOLUTION |
WIDTHxHEIGHT or WIDTHxHEIGHT@REFRESH |
display's native mode | Output resolution, e.g. 1920x1080 or 1920x1080@60 (refresh rate in Hz). Leave unset to use the display's native (preferred) mode. |
DISPLAY_SCALE |
[Integer] ≥ 1 | 1 |
Integer output scaling factor (e.g. 2 for HiDPI displays). |
DISPLAY_ROTATION, DISPLAY_RESOLUTION and DISPLAY_SCALE configure the output and are applied to
the first connected display detected (the block auto-detects DRM connectors, so you do not need
to know the platform-specific connector name like HDMI-A-1 or DSI-1). The detected connector
names are logged on startup.
Rotation rotates the displayed content clockwise by the given number of degrees — DISPLAY_ROTATION=90
turns the picture 90° clockwise (the top edge moves to the right), the same way the rotation setting
behaves on macOS/Windows and in the v2 block. It is expressed in degrees (not compositor-specific
terms like left/right) so the interface stays independent of the underlying compositor. Touch
input rotates automatically with the display — the compositor applies the matching transform to
libinput touch devices, so there is no separate touch-calibration setting to configure.
When using
DISPLAY_ROTATION=90or270, setDISPLAY_RESOLUTIONto the panel's native (pre-rotation) mode explicitly (e.g.1920x1080) rather than leaving it unset, as the compositor positions the output using the post-rotation width.
Example — a portrait kiosk on a 1080p panel rotated 90° clockwise:
services:
display:
image: bh.cr/balenalabs/display-<arch>
privileged: true
volumes:
- display-socket:/run
labels:
io.balena.features.dbus: '1'
environment:
DISPLAY_ROTATION: 90
DISPLAY_RESOLUTION: 1920x1080Multiple displays are not yet supported. Only the first connected display is configured by these variables; see
docs/todo-multi-display.mdfor the current status. For custom multi-output layouts today, provide your ownweston.iniviaWESTON_INI_PATH(see Advanced Configuration).
If the configurations provided does not suit your use case such as complex output management—such as requiring complex output management, multiple independent displays, or custom launchers—you must provide a custom weston.ini via a Dockerfile override.
The following snippets demonstrate how to override the configuration by inheriting from the base image and copying a custom weston.ini. You must also configure WESTON_INI_PATH to the absolute path of your weston.ini file for it to take effect.
./display/Dockerfile.template
FROM bh.cr/balenasolutions/display-%%BALENA_ARCH%%
# Inject your custom Weston configuration
COPY weston.ini /etc/weston/weston.iniRenders a hardware-accelerated OpenGL ES spinning gears demo using eglgears_wayland from Mesa utils. Demonstrates EGL/OpenGL rendering over Wayland with a live FPS/CPU overlay via GALLIUM_HUD.
Runs the GTK4 demo suite over Wayland, demonstrating interactive touch input. The specific demo can be configured via the DEMO environment variable (default: drawingarea).
This project uses a block pattern: a single display container runs the Weston compositor and exposes a Wayland socket via a shared Docker volume. Any number of client containers can connect to it.
- Runs the Weston compositor, managing graphics hardware directly via DRM
- Creates a Wayland socket at
/run/user/0/wayland-0for client connections - Handles GPU rendering through the DRM backend
- Releases the Plymouth DRM lock on startup to ensure exclusive GPU access
- Any Wayland-compatible application (GTK4, Qt, LVGL, OpenGL, etc.)
- Connects to the display block via the shared Wayland socket
- Renders UI through the Wayland protocol (hardware-accelerated)
- Polls for the socket before attempting connection
Both containers share a Docker volume (display-socket) mounted at /run, making the Weston socket accessible to client containers.
┌─────────────────────────────────────────────────────────────┐
│ Docker Compose │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌────────────────────────┐ │
│ │ display (block) │ │ your-app (client) │ │
│ ├──────────────────────┤ ├────────────────────────┤ │
│ │ Weston Compositor │ │ Wayland Client │ │
│ │ DRM Backend (GPU) │◄─────┤ (GTK4 / Qt / LVGL / │ │
│ │ │ │ OpenGL / anything) │ │
│ │ /usr/bin/entry.sh │ │ │ │
│ └──────────┬───────────┘ └───────────┬────────────┘ │
│ │ │ │
│ └──────────────────────────────┘ │
│ Shared volume: /run │
│ (Wayland socket: wayland-0) │
└─────────────────────────────────────────────────────────────┘
GPU acceleration is enabled through:
- DRM Backend — Weston connects directly to the GPU via
/dev/dri - Mesa Graphics Libraries — Provides OpenGL/Vulkan drivers
The display container requires:
privileged: true— DRM master access/dev/dri— GPU device accessio.balena.features.dbus: '1'— D-Bus access (for stopping Plymouth)
Common issues:
wayland-0 socket not found→ Weston failed to start; check display logsfailed to load drm driver→ GPU drivers not installed or hardware not supported[WARN] D-Bus socket not found→ Missingio.balena.features.dbus: '1'label on display service; Plymouth may still hold the DRM lock