Post

Docker Images Do Not Bring a Kernel (and Synology Taught Me the Hard Way)

I spent a while being simultaneously furious and relieved about Docker on a Synology NAS. The short version: building on the Synology was misery; building on a Mac, pushing to Docker Hub, and pulling on the NAS worked. That felt like witchcraft until the real lesson clicked.

It was never about Debian versus Alpine. It was about the kernel.


The setup

I package a headless Camoufox / Bun service as a Docker image. The same image (or the same Dockerfile) behaved differently depending on where I used it:

HostRoughlyBuild / Bun
Mac (Docker Desktop)Darwin for the Mac; Linux 6.12.76-linuxkit in the Desktop VMWorks
UbuntuLinux 6.17.0-35-genericWorks
Alpine Linux VPSLinux 6.6.54-0-virtWorks
Synology DS920+Linux 4.4.302+ (DSM)Build fails

On the DS920+ you still get something like 4.4.302+ from uname -r. Containers share that kernel. Synology backports security fixes, but it does not give you a modern syscall surface.


What actually broke

Modern Bun (from about 1.2.3 onwards) leans on newer Linux behaviour — notably the statx syscall, which showed up in Linux 4.11. DSM’s 4.4 answers with function not implemented. Build steps that run Bun (bun install, bunx, fetching browser bits) die on the NAS.

There is a second hardware wrinkle on the DS920+: the Celeron J4125 has no AVX2. That can mean Illegal instruction with a non-baseline x64 Bun binary. Annoying, but secondary. The kernel story is the one that explains “every OS works except Synology.”

Alpine in the Dockerfile does not help. Neither does canary Bun. Neither does Docker-in-Docker. All of those still run on the host kernel.


Why “build on Mac, run on Synology” feels magical

A docker build is not a theoretical recipe. Every RUN line executes on whatever kernel that Docker daemon sits on.

  • Build on Synology → Bun runs on 4.4 → boom during install/fetch.
  • Build on the Mac → those same steps run inside Docker Desktop’s Linux VM (kernel 6.x) → image finishes with node_modules, Camoufox, yt-dlp already baked in.
  • Push to Hub, pull on Synology → the NAS only unpacks layers and starts a process. It never re-runs the install.

So Container Manager on DSM is a poor builder and a serviceable runner of pre-baked images. Once I treated the Mac (or any modern Linux box) as the build farm and the NAS as the place that only pulls, the anger made sense — and so did the relief.

Runtime still shares Synology’s kernel. If Bun later hits a path that needs those syscalls, you can still get surprises. In practice, a service that was fully installed elsewhere and mostly serves HTTP can “work enough.” That luck is not the same as “DSM is fine for building Bun.”


The hope I had to let go of

I really wanted a Docker image that “had the latest kernel.”

That is not how containers work. An image is userspace: binaries, libraries, package metadata. FROM alpine:edge or oven/bun:canary-alpine never ships or upgrades Linux. The container always calls into the host kernel.

Own kernel?
Container / DockerNo — shares the host
Virtual machineYes

So my experimental Alpine image might change musl vs glibc, package names, and Bun tags. It does not turn a DS920+ into kernel 6.

Docker-in-Docker fails for the same reason: you nest the daemon, not the kernel. Privileged dind on DSM is still 4.4.


If the Synology is your only machine

Then you need a guest OS with a modern kernel, not a cleverer Dockerfile.

On Synology that means Virtual Machine Manager: install Ubuntu, Debian, or Alpine in a VM (they ship kernel 6.x), install Docker inside the guest, and build there. You are not compiling a custom kernel from scratch — you are running a normal distro that already has one.

Trade-off: VMs on a DS920+ burn RAM and CPU, and Camoufox is not light. If you have any other host (Mac, VPS, CI), keep using that for builds and let the NAS only pull.


How to check what you actually have

bash
uname -r
  • On Ubuntu or an Alpine VPS, that is the kernel containers share.
  • On Synology, expect 4.4.x.
  • On a Mac, uname -r is Darwin (for example 25.x under a recent macOS). That is not what Linux containers use. Ask the Desktop VM:
bash
docker run --rm alpine uname -r

You should see a Linux 6.x-style version (often with linuxkit). That is the kernel your docker build on the Mac actually sees.


Practical workflow I landed on

  1. Develop and build on Mac / Ubuntu / Alpine VPS (kernel 6+).
  2. Multi-arch push when needed (linux/amd64 for the NAS, linux/arm64 for Apple Silicon), e.g. via buildx and a small push script.
  3. On Synology: pull and run only — do not treat Container Manager as a Bun build box.
  4. Reach for a VM on the NAS only if there is truly no other builder.

Optional footnote for the stubborn: pinning an ancient Bun (for example 1.2.2) can limp along on old kernels for some workloads. That is a compatibility trap, not a strategy I would bet a Camoufox image on long term.


One sentence to keep

Docker images change the userspace; only the host (or a VM) chooses the kernel.

Synology’s old kernel made that painfully obvious. Building elsewhere and shipping an image is not a hack — for this class of NAS, it is the correct architecture.