Building and Loading a Custom Kernel Module with DKMS
Packaging a kernel module with DKMS so it automatically rebuilds against every new kernel version, instead of breaking silently on the next kernel update.
A kernel module compiled against one specific kernel version’s headers only works with that exact version — the kernel’s internal ABI isn’t guaranteed stable across versions, so a module built for one kernel typically won’t load against a different one at all. DKMS (Dynamic Kernel Module Support) exists specifically to solve this: it keeps a module’s source around and automatically triggers a rebuild against each newly installed kernel, rather than requiring a manual rebuild-and-reinstall cycle every time the kernel updates.
Why this matters beyond just convenience
Without DKMS, a manually-built out-of-tree module is a ticking time bomb of sorts: it works fine until the next routine kernel update, at which point it silently fails to load (or the system fails to boot correctly, if the module is essential — a display driver being the classic case), and the failure often isn’t obvious until you’re already in the middle of troubleshooting a broken boot with no immediate explanation. DKMS converts this into a routine, automatic rebuild that happens as part of the kernel update process itself.
Setting up the source tree for DKMS
DKMS expects a module’s source in a specific layout, typically under /usr/src/<module-name>-<version>/:
/usr/src/mymodule-1.0/
├── dkms.conf
├── Makefile
└── mymodule.c
The dkms.conf file is what actually tells DKMS how to build and name the module:
# /usr/src/mymodule-1.0/dkms.conf
PACKAGE_NAME="mymodule"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="mymodule"
DEST_MODULE_LOCATION[0]="/kernel/drivers/misc"
AUTOINSTALL="yes"
AUTOINSTALL="yes" is what makes DKMS automatically rebuild this module for every new kernel installed afterward, without needing to manually re-trigger anything each time — the entire point of using DKMS instead of a one-off manual build.
Registering the module with DKMS
dkms add -m mymodule -v 1.0
This registers the module (pointed at the source tree above) with DKMS’s tracking database, without building it yet — a distinct, separate step from actually compiling.
Building and installing for the current kernel
dkms build -m mymodule -v 1.0
dkms install -m mymodule -v 1.0
build compiles the module against the currently running kernel’s headers; install places the resulting .ko file into the correct location under /lib/modules/$(uname -r)/ and updates the module dependency database, after which it’s loadable like any other module:
modprobe mymodule
Confirming it’s tracked correctly
dkms status
This lists every DKMS-managed module along with which kernel versions it’s currently built and installed for — mymodule/1.0, 6.8.0-31-generic: installed confirms both that the build succeeded and that DKMS correctly registered it against the specific running kernel version, rather than just assuming a clean build implies a correct install.
What happens automatically on the next kernel update
When a new kernel package installs, the package manager’s post-install hooks (which DKMS registers itself into during its own installation) trigger DKMS to rebuild every registered module against the newly installed kernel’s headers automatically — no manual intervention required, which is the entire value proposition. Confirming this worked after a kernel update, rather than assuming it did, is worth doing at least once:
dkms status
should now show the module built and installed against both the old and new kernel versions (DKMS keeps builds for multiple installed kernels simultaneously, since the system may still boot into either one).
When a rebuild fails against a new kernel
Not every module survives every kernel update cleanly — a kernel version that changes an internal API a module depends on can break the build even with DKMS correctly triggering the rebuild attempt. The actual compiler error is in the build log:
cat /var/lib/dkms/mymodule/1.0/build/make.log
This is the first place to look when dkms status shows a module as failed for a specific kernel version rather than installed — the log contains the real error, whether that’s a genuine incompatibility needing a source-level fix, or something more mundane like missing kernel headers for the new version (apt install linux-headers-$(uname -r) or the equivalent, if that’s the actual cause).
Why this is worth doing even for a module you only use occasionally
It’s tempting to skip DKMS packaging for a module you don’t rely on constantly and just rebuild manually the rare times you need it — but the actual failure mode this leads to is discovering the module is broken at exactly the inconvenient moment you need it working, with no advance warning, rather than DKMS having already handled the rebuild transparently in the background across every kernel update in between.