Digital Signage Raspberry Pi Setup

Introduction

In this tutorial, we will create a lightweight and modular digital signage solution for the Raspberry Pi. The setup includes a digital signage player and, optionally, a local web-based CMS. The system is designed to start automatically on boot and recover seamlessly after unexpected shutdowns or crashes.

Requirements

  • Raspberry Pi 4 or 5 It will work on Raspberry Pi 3, too, but it is not recommended as HD videos will stutter occasionally and overall performance is poor.
  • RAM: 512 MB is enough for running garlic-player alone. If you also plan to run garlic-hub (the CMS) on the same device, at least 2 GB is recommended.
  • microSD card: at least 16 GB. If you plan to store larger media files locally or run garlic-hub alongside the player, 32 GB or more is recommended. For better reliability and performance, consider booting from a USB SSD or, on the Raspberry Pi 5, an NVMe SSD via a PCIe M.2 HAT, instead of a microSD card.
  • Raspberry Pi Imager Personally I like to use it as it makes it straightforward to install the OS and set default settings like ssh access, wi-fi, remote access, etc. You will find it on raspberrypi.com/software/
  • Raspberry Pi OS Lite 64 Bit (based on Debian 13 Trixie) The garlic-player is an 64 Bit app. At the same time the lite is easy as we need to install only the required things to make it less vulnerable
  • labwc The Lab Wayland Compositor) is a lightweight, fast, and rock-solid window manager for Wayland inspired by Openbox.
  • garlic-player as Arm 64 Bit AppImage
  • garlic-hub (optional) A Digital Signage CMS which will work with the garlic-player.
  • Apache Webserver for the garlic-hub
  • PHP8.4 for the garlic-hub

Why labwc is better suited for Kiosk Applications than Cage

Although Cage is designed for single-application kiosks, applications based on QtWebEngine and Xwayland are more reliable under labwc. Labwc provides lightweight Wayland window management while offering greater flexibility for complex desktop applications.

  1. Better Compatibility with QtWebEngine

    Labwc handles auxiliary windows and background processes more gracefully, reducing issues that can occur with Chromium-based applications.

  2. More Mature Xwayland Support

    For libVLC-based ARM64 applications such as garlic-player, labwc provides a more reliable Xwayland environment.

  3. Improved Popup Handling

    Dialogs, dropdowns, and other transient windows can be managed through configuration rules, helping maintain a stable kiosk experience.

While Cage offers a stricter single-window model, labwc can provide the same kiosk behavior by disabling window-management shortcuts and forcing the application into fullscreen mode.

Install Base System

Insert a microSD card with at least 16 GB capacity into the computer. I have some good performance experiences with Sandisk Ultra cards.

In Raspberry Pi Imager click **Choose OS** → **Raspberry Pi OS (other)** → **Raspberry Pi OS Lite (64-bit)**

I encourage you to enable **SSH**, so the remaining installation and configuration steps can be performed remotely instead of directly on the Pi.

Set the remaining preferred options (hostname, username/password, Wi-Fi credentials if needed), then click **Save**, followed by **Write** to flash the image to the SD card.

After putting the card into the Raspberry Pi and let it boot you will see the IP in the top of the screen. Open a ssh connection and start first with a system update.

sudo apt update && sudo apt upgrade -y
After the system update, install the required packages:
sudo apt install labwc seatd libfuse2 vlc libnss3 -y

The app seatd manages access to input devices and the display (the "seat") for programs that don't run under a traditional login manager. Without it, Wayland compositors like labwc cannot access the keyboard, mouse, or GPU when started outside of a normal desktop session, which is the case in this minimal, kiosk-style setup.

Package libnss3 (Network Security Services) provides the cryptographic and TLS/SSL libraries that QtWebEngine relies on for HTTPS connections. It's required for garlic-player to correctly load web content over secure connections. Normally it is bundled in the AppImage but in our setup we need the native from Trixie.

VLC from Trixie is needed for correct hardware acceleration. Explanation will come below.

enable seat and add your user into the video group

sudo systemctl enable --now seatd
sudo usermod -aG video,input YOUR_USER

A minimal install does not include a sound server. Without one, ALSA alone cannot play regular PCM audio through the vc4-hdmi driver: it only exposes the raw IEC958_SUBFRAME_LE format. Installing and enabling PipeWire, as shown below, adds the missing layer that converts standard audio into a format the HDMI driver accepts.

sudo apt install pipewire pipewire-pulse wireplumber pipewire-alsa -y
and activate it: It is important to do this as non-root
systemctl --user enable --now pipewire pipewire-pulse wireplumber

It is required to add the broadcom codec driver otherwise you will have no hardware acceleration for videos.

echo "bcm2835-codec" | sudo tee -a /etc/modules

Hardware Decoding Limitations

Both the Raspberry Pi 4 and 5 only provide hardware-accelerated decoding for H.265 (HEVC) at 4K. H.264 hardware decoding is limited to 1080p on the Pi 4 and is not hardware-accelerated at all on the Pi 5. Playing 4K H.264 content will fall back to software decoding, which the CPU cannot handle smoothly at that resolution.

If you plan to show 4K content, encode it as H.265/HEVC. For H.264 content, keep the resolution at 1080p or below.

There are several changes made in this section but they will only take effect after a reboot. Reboot the Raspberry Pi now before continuing.

sudo reboot

Install the Garlic-Player

First you download the aarch64 AppImage then make it executable and then extract it. You should do this with your normal user.

wget https://garlic-signage.com/downloads/garlic-player-aarch64.AppImage
chmod +x garlic-player-aarch64.AppImage
./garlic-player-aarch64.AppImage --appimage-extract
		

Why the Extraction?

We need to configure the Garlic-Player. As mentioned in the intro we want a minimal system. The AppImage is created on Debian 12 (Bookworm) for maximum compatibility to run on as much linux distribution as possible. Unfortunately, this solution is not perfect, and you may encounter some issues.

To create an efficient system you need to extract AppImage and remove some libs to use system libs like vlc and other for better performance and compatibility.

mkdir -p ~/squashfs-root/usr/lib/bak

cd ~/squashfs-root/

mv usr/lib/vlc usr/lib/libavc1394.so* usr/lib/libavcodec.so* usr/lib/libavdevice.so* usr/lib/libavfilter.so* usr/lib/libavformat.so* usr/lib/libavif.so* usr/lib/libavutil.so* usr/lib/libgio-2.0.so* usr/lib/libglib-2.0.so* usr/lib/libgmodule-2.0.so* usr/lib/libgobject-2.0.so* usr/lib/libnspr4.so* usr/lib/libnss3.so* usr/lib/libnssutil3.so* usr/lib/libplc4.so* usr/lib/libplds4.so* usr/lib/libvlccore.so* usr/lib/libvlc_pulse.so* usr/lib/libvlc.so* usr/lib/libvlc_vdpau.so* usr/lib/libvlc_xcb_events.so* usr/lib/bak/

# and check if everything is moved
ls usr/lib/bak/

# should result in this
libavc1394.so.0
libavc1394.so.0.3.0
libavcodec.so
libavcodec.so.59
libavcodec.so.59.37.100
libavdevice.so
libavdevice.so.59
libavdevice.so.59.7.100
libavfilter.so
libavfilter.so.8
libavfilter.so.8.44.100
libavformat.so
libavformat.so.59
libavformat.so.59.27.100
libavif.so.15
libavif.so.15.0.1
libavutil.so
libavutil.so.57
libavutil.so.57.28.100
libgio-2.0.so.0
libglib-2.0.so.0
libgmodule-2.0.so.0
libgobject-2.0.so.0
libnspr4.so
libnss3.so
libnssutil3.so
libplc4.so
libplds4.so
libvlccore.so.9
libvlccore.so.9.0.1
libvlc_pulse.so.0
libvlc.so
libvlc.so.5
libvlc.so.5.6.1
libvlc_vdpau.so.0
libvlc_xcb_events.so.0
				

Probably you can move more files, but these are the minimum files.

Now you can start the first test. Open a second ssh terminal log in and start labwc with

labwc &

You should see now a black screen on the Raspberry with a mouse cursor.

Now you can start the garlic-player and configure the content URL with the CMS of your choice.

QT_QPA_PLATFORM=xcb DISPLAY=:0 VLC_PLUGIN_PATH=/usr/lib/aarch64-linux-gnu/vlc/plugins ./AppRun -m fullscreen
Set Content URL Setup
Set Content URL of the garlic-player

To change the content URL later, press Ctrl+S while garlic-player is running to reopen the setup screen. See the garlic-player command line options & hotkeys for further configurations.

Why xcb Platform?

libVLC currently has no public API to embed video output into a native Wayland surface. Because of this, garlic-player relies on XWayland, the X11 compatibility layer built into labwc, rather than native Wayland rendering. This is why QT_QPA_PLATFORM=xcb is used instead of wayland.

Install Autoloader and Crash Recovery

We have now installed a minimalist Window manager and a digital signage media player, but we want the player starts automatically on boot and recovers from crashes.

To start labwc in a kiosk mode you need a service. Replace niko with the username of the user that should run the player.

nano -w /etc/systemd/system/labwc-kiosk.service
[Unit]
Description=Labwc Kiosk
After=systemd-user-sessions.service seatd.service

[Service]
User=niko
Group=niko
PAMName=login
Environment=XDG_RUNTIME_DIR=/run/user/1000
ExecStartPre=/bin/mkdir -p /run/user/1000
ExecStartPre=/bin/chown niko:niko /run/user/1000
ExecStart=/usr/bin/labwc
Restart=always
RestartSec=2
TTYPath=/dev/tty1
StandardInput=tty
StandardOutput=journal

[Install]
WantedBy=graphical.target

Set the service to start after the graphical target.

systemctl enable labwc-kiosk.service
systemctl set-default graphical.target

After start of labwc the player must be started. Create a player service for this.

nano -w ~/.config/systemd/user/garlic-player.service
[Unit]
Description=Garlic Player
After=graphical-session.target

[Service]
Environment=QT_QPA_PLATFORM=xcb
Environment=DISPLAY=:0
Environment=VLC_PLUGIN_PATH=/usr/lib/aarch64-linux-gnu/vlc/plugins
ExecStart=/home/niko/squashfs-root/AppRun -m fullscreen
Restart=always
RestartSec=3

[Install]
WantedBy=graphical-session.target
systemctl --user daemon-reload

The player service must start after the graphical-session.target (labwc). To do this put the service in labwc autostart.

nano -w ~/.config/labwc/autostart
#!/bin/sh
systemctl --user start garlic-player.service

Finally the player service must be set as executable

chmod +x ~/.config/labwc/autostart

At this point, garlic-player will start automatically and keep restarting, but it has no content URL configured yet, so it will just show its setup screen. This gets resolved once garlic-hub is installed and a content URL is configured in the following sections.

Install Garlic-Hub CMS

If you want to install the CMS alongside the player, follow these steps:


			sudo apt install -y apache2 php libapache2-mod-php php-cli php-common php-zip php-curl php-xml php-mbstring php-intl php-sqlite3 php-gd php-imagick ffmpeg imagemagick sqlite3 zip unzip libzip-dev libcurl4-openssl-dev libxml2-dev libmagickwand-dev libicu-dev libonig-dev ghostscript composer
		

You will need a directory to place garlic-hub. /var/www/garlic-hub is the right place. Check for the newest stable of garlic-hub. This is currently the v1.1.1.

wget https://github.com/garlic-signage/garlic-hub/archive/refs/tags/v1.1.1.tar.gz -O /tmp/garlic-hub.tar.gz
sudo mkdir -p /var/www/garlic-hub
sudo tar -xzf /tmp/garlic-hub.tar.gz -C /var/www/garlic-hub --strip-components=1
sudo chown -R www-data:www-data /var/www/garlic-hub

To make handling easier add your user to the www-data an vice versa

sudo usermod -aG www-data YOUR_USER
sudo usermod -aG YOUR_USER www-data
sudo chown -R YOUR_USER:www-data /var/www/garlic-hub
sudo chmod -R g+rwX /var/www/garlic-hub
newgrp www-data
		

Now create the var directories

mkdir -p \
  /var/www/garlic-hub/var \
  /var/www/garlic-hub/var/cache \
  /var/www/garlic-hub/var/logs \
  /var/www/garlic-hub/var/weblogs \
  /var/www/garlic-hub/var/keys \
  /var/www/garlic-hub/var/sessions \
  /var/www/garlic-hub/public/var/mediapool \
  /var/www/garlic-hub/public/var/mediapool/thumbs \
  /var/www/garlic-hub/public/var/mediapool/originals \
  /var/www/garlic-hub/public/var/templates \
  /var/www/garlic-hub/public/var/templates/thumbs \
  /var/www/garlic-hub/public/var/templates/originals \
  /var/www/garlic-hub/public/var/playlists \
  /var/www/garlic-hub/public/var/playlists/items \
  /var/www/garlic-hub/public/var/playlists/items/thumbs \
  /var/www/garlic-hub/public/var/playlists/items/originals

cd /var/www/garlic-hub
		

Install composer libs

composer install --no-dev --optimize-autoloader

Garlic-hub uses these keys internally for signing and encrypting authentication tokens and other sensitive data handled by its crypto class.

openssl genpkey -algorithm RSA -out var/keys/private.key -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in var/keys/private.key -out var/keys/public.key
php vendor/bin/generate-defuse-key > var/keys/encryption.key

copy the .env file from default and add an app secret

cp dockerapp-configs/env.edge .env
echo "APP_SECRET=$(openssl rand -hex 16)" >> /var/www/garlic-hub/.env

now you need to edit the .env file and replace the line:

DB_MASTER_PATH=/var/www/var/garlic-hub.sqlite

with

DB_MASTER_PATH=/var/www/garlic-hub/var/garlic-hub.sqlite

create a sqlite database

php bin/console.php db:migrate

and set the var drectories readable for www-data. This command outputs Migration succeed. on success. If it fails instead, check that the directory permissions were set correctly in the previous steps.

sudo chown -R www-data:www-data /var/www/garlic-hub/public/var /var/www/garlic-hub/var
sudo chmod -R 775 /var/www/garlic-hub/public/var /var/www/garlic-hub/var

It is alos necessary to teach apache to set the correct permissions for files created with ww-data

sudo mkdir -p /etc/systemd/system/apache2.service.d
sudo nano /etc/systemd/system/apache2.service.d/override.conf
[Service]
UMask=0002

save the changes and

sudo systemctl daemon-reload
sudo systemctl restart apache2

Configure Apache2

This tutorial uses Apache for its simplicity. If you prefer Caddy instead, a ready-to-use Caddyfile is included in the root directory of garlic-hub.

Activate rewrite module


			sudo a2enmod rewrite

Copy the default http.conf to Apache config

dockerapp-configs is a directory included in the garlic-hub repository containing default configuration skeletons, including the .env template and server configs used below.
sudo cp dockerapp-configs/http.conf /etc/apache2/sites-available/garlic-hub.conf
sudo nano -w /etc/apache2/sites-available/garlic-hub.conf

and change it to this


ServerName _


    ServerAdmin admin@example.com
    DocumentRoot "/var/www/garlic-hub/public"
    
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    

    ErrorLog /var/log/apache2/garlic-hub.error.log
    CustomLog /var/log/apache2/garlic-hub.access.log combined



Enable the garlic-hub site, deactivate the default site and reload apache

sudo a2ensite garlic-hub.conf
sudo a2dissite 000-default.conf
systemctl reload apache2

Basic Security Considerations

garlic-hub is now reachable on port 80 to anyone on the local network. Before going into production, consider the following:

  • Restrict access to garlic-hub to your local network using a firewall, e.g. with ufw, allowing only trusted devices or subnets.
  • If garlic-hub needs to be reachable from outside the local network, place it behind a reverse proxy with HTTPS rather than exposing port 80 directly.
  • Consider enabling automatic security updates with unattended-upgrades, since the device may run unattended in the field for extended periods.

Call Garlic-hub

You can call garlic-hub with your favorite browser and enter the ip address of your raspberry pi. There is documentation about garlic-hub essentials and the first-start.

At this point, the setup is complete: garlic-player starts automatically on boot, recovers from crashes, and displays content managed through garlic-hub. From here, log in to garlic-hub to create your first playlist and assign it to the player.