Touchscreen Kiosk Dashboard on ZimaBoard 2 using Docker (X.Org + Chromium)

Eva Wong is the Technical Writer and resident tinkerer at ZimaSpace. A lifelong geek with a passion for homelabs and open-source software, she specializes in translating complex technical concepts into accessible, hands-on guides. Eva believes that self-hosting should be fun, not intimidating. Through her tutorials, she empowers the community to demystify hardware setups, from building their first NAS to mastering Docker containers.

Hey everyone,

I wanted to share how I got a fullscreen touchscreen kiosk running on my ZimaBoard 2 connected to a 22" touch monitor via HDMI.

Since ZimaOS has no package manager (apt doesn't exist), the trick is to do everything inside Docker containers.

My Setup

ZimaBoard 2 1664 (Intel N100, 16 GB RAM)

ZimaOS v1.5.4

22" touch monitor via HDMI + USB-C→USB-A touchscreen input

A close-up shot of a white and red Thermaltake Tower 900 PC case, featuring custom blue LED lighting and a detailed view of internal components, including an Intel processor and memory.

The Approach

ZimaOS is Buildroot-based — no apt, no Xorg, no desktop environment. Instead, we run a Debian container with Xorg modesetting driver + Chromium in kiosk mode, with /dev/dri passed through. A separate nginx:alpine container serves the dashboard files.

Step 1 — Add your user to the docker group

Open the web terminal at http://your-zima-ip:7681 and run:

bash

sudo usermod -aG docker YOUR_USERNAME

Then reconnect SSH for the change to take effect.

Step 2 — Create the files

bash

mkdir -p /DATA/AppData/kiosk/dashboard

/DATA/AppData/kiosk/Dockerfile:

text

FROM debian:bookworm-slim
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
RUN apt-get update && \
apt-get install -y \
xserver-xorg-core \
xserver-xorg-input-libinput \
openbox chromium \
x11-xserver-utils xinit \
fonts-noto-core && \
apt-get install -y -t bookworm-backports libgl1-mesa-dri && \
rm -rf /var/lib/apt/lists/*
COPY xorg.conf /etc/X11/xorg.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Why backports Mesa? The Intel N100 (Alder Lake-N, PCI ID 0x46d4) is only supported from Mesa 23.0+. Debian bookworm ships 22.3. Backports provides 25.x.

/DATA/AppData/kiosk/xorg.conf:

text

Section "Device"
Identifier "Intel"
Driver "modesetting"
Option "AccelMethod" "none"
EndSection

Section "Screen"
Identifier "Default"
Device "Intel"
EndSection

Section "InputDevice"
Identifier "Touchscreen"
Driver "libinput"
Option "Device" "/dev/input/event8"
EndSection

Section "ServerLayout"
Identifier "Default"
Screen "Default"
InputDevice "Touchscreen" "CorePointer"
EndSection

Find your touch device event number with:

bash

cat /proc/bus/input/devices | grep -A3 -i touch

/DATA/AppData/kiosk/entrypoint.sh:

text

#!/bin/sh
Xorg :0 vt1 -s 0 -dpms -nolisten tcp &
sleep 4
export DISPLAY=:0
xset s off && xset -dpms && xset s noblank
openbox --sm-disable &
sleep 1
exec chromium \
--kiosk --no-sandbox --disable-gpu \
--disable-dev-shm-usage \
--touch-events=enabled \
--no-first-run --disable-infobars \
"${KIOSK_URL:-http://localhost:8888}"

/DATA/AppData/kiosk/nginx.conf:

text

server {
listen 8888;
root /usr/share/nginx/html;
index index.html;
}

Put your dashboard HTML/CSS/JS files into /DATA/AppData/kiosk/dashboard/.

Top-down view of the ZimaBoard single board server, highlighting its PCIe expansion slot, dual Ethernet ports, and SATA connectors for DIY project integration.

Step 3 — Build and Run

Build (note: docker build needs --config /tmp on ZimaOS):

bash

docker --config /tmp build -t kiosk:latest /DATA/AppData/kiosk

Dashboard web server:

bash

docker --config /tmp run -d \
--name dashboard-server \
--restart unless-stopped \
--network host \
-v /DATA/AppData/kiosk/dashboard:/usr/share/nginx/html:ro \
-v /DATA/AppData/kiosk/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:alpine

Kiosk display:

bash

docker --config /tmp run -d \
--name jarvis-kiosk \
--restart unless-stopped \
--privileged \
--network host \
-e KIOSK_URL=http://localhost:8888 \
-v /DATA/AppData/kiosk/xorg.conf:/etc/X11/xorg.conf:ro \
-v /DATA/AppData/kiosk/entrypoint.sh:/entrypoint.sh:ro \
-v /run/udev:/run/udev:ro \
kiosk:latest

Both containers have --restart unless-stopped so they survive reboots automatically.

Touchscreen Click Fix (important!)

Xorg's libinput maps touch as pointer (mouse) events. Chromium then applies a tiny movement threshold for click — even a 1px finger shift prevents it from firing. If your dashboard uses onclick handlers, add this JS to replace them with pointerup + a 20px threshold:

js

(function touchFix() {
const downs = {};
document.addEventListener('pointerdown', e => {
downs[e.pointerId] = { x: e.clientX, y: e.clientY };
}, { passive: true });
function isTap(e) {
const d = downs[e.pointerId];
return !d || (Math.abs(e.clientX - d.x) < 20 && Math.abs(e.clientY - d.y) < 20);
}
function run(fn) { try { new Function(fn)(); } catch(ex) {} }
function patchButtons(root) {
root.querySelectorAll('button[onclick]').forEach(btn => {
const oc = btn.getAttribute('onclick');
btn.removeAttribute('onclick');
btn.addEventListener('pointerup', e => { e.stopPropagation(); if (isTap(e)) run(oc); });
});
}
window.addEventListener('load', () => {
document.querySelectorAll('.widget[onclick]').forEach(el => {
const oc = el.getAttribute('onclick');
el.removeAttribute('onclick');
el.addEventListener('pointerup', e => { if (isTap(e) && !e.target.closest('button')) run(oc); });
});
patchButtons(document.body);
});
new MutationObserver(muts => muts.forEach(m =>
m.addedNodes.forEach(n => { if (n.nodeType === 1) patchButtons(n); })
)).observe(document.body, { childList: true, subtree: true });
})();

Also add to your CSS to hide the mouse cursor on a touchscreen:

css

{ cursor: none !important; touch-action: manipulation; }

A 22-inch Pisichen touch monitor connected to a ZimaBoard via HDMI and USB, displaying a custom smart home dashboard with interactive widgets.

Troubleshooting

Problem Fix
docker build fails with unknown flag Use docker --config /tmp build …
Black screen / Basic output test failed Use Xorg modesetting, not Wayland/cage
Mesa doesn't support N100 Install libgl1-mesa-dri from bookworm-backports
Touch input not detected Check event number with /proc/bus/input/devices, update xorg.conf
Dashboard shows ZimaOS login Port 80 is taken by ZimaOS — use a different port (888 works)

My Always-On Home Assistant Setup (Reply)

I built a power-efficient home assistant setup that's available 24/7 without having my main rig burning electricity around the clock. The secret weapon is a Zima acting as the always-on brain, while my full PC sleeps in hibernation until it's actually needed.

The Main PC

Here's what's inside the primary machine:

CPU: AMD Ryzen 9 9950X3D

Motherboard: ASUS ROG Crosshair X870E Glacial

RAM: Acer Predator Hermes RGB DDR5 96GB (2×48GB) 6000MHz CL28

Storage: Crucial T710 4TB NVMe M.2 PCIe 5.0 Gen5

GPU (Gaming): ASUS ROG Astral GeForce RTX 5080 16GB

GPU (AI): Intel Arc Pro B70 32GB (waiting on release)

PSU: Corsair RM1200x

Case: Thermaltake Tower 900

The Zima Setup (The Always-On Hub)

The Zima runs 24/7 and handles everything lightweight — home assistant duties, camera feeds, and basic AI queries using Groq free tier via the cloud.

Connected peripherals directly to the Zima:

Pisichen 22" Touch Screen monitor via HDMI → miniDP adapter + one USB-C cable for touch input

Edifier G1000 II speakers through an external USB-A to 3.5mm soundcard

DAMAO DGM20S microphone via USB-A

Storage:

Seagate IronWolf Pro 20TB — ''black hole'' NAS drive

Seagate IronWolf Pro 1TB — main NAS drive

Networking:

Zima connected to the router via Ethernet

Second Ethernet cable runs directly from the Zima to the PC for fast local transfers

A comprehensive home automation workspace featuring a ZimaBoard powered touchscreen kiosk, professional speakers, and a minimalist desk setup for 24/7 monitoring.

The Smart Part: Wake-on-LAN

Here's the magic — when someone asks a question that's too complex for Gemini to handle well, the Zima sends a Wake-on-LAN packet to bring the main PC out of hibernation. Once it's up, it connects to a local 70B parameter LLM running on the Intel Arc Pro B70. After the task is done, the PC goes back to sleep.

The result: a home assistant that's always responsive, but the big rig only wakes up when there's actual heavy lifting to do. Power bills stay sane, and I still get access to a seriously capable local model when I need it.

Huge shoutout to Claude Code and Perplexity — honestly, without their help I would have never been able to put this whole project together on my own. They were invaluable throughout the entire process.

I hope this information helps anyone crazy enough to take on a similar project — feel free to share it wherever you like!

Zima Campaign Hub

More to Read

Get More Builds Like This

Stay in the Loop

Get updates from Zima - new products, exclusive deals, and real builds from the community.

Stay in the Loop preferences

We respect your inbox. Unsubscribe anytime.