
Building an IoT device that respects the GNU General Public License isn’t just about ticking a legal box. It’s a quiet stand on how we choose to share technology. I’ve been designing connected hardware for years, and the moment a Linux kernel or GPL-licensed userspace lands inside your firmware, you’ve made a promise to the people who use your device. This piece walks through the hardware and software architecture calls, how to shape your build system, and the physical side of delivering source code—all from an engineer who treats compliance as a feature, not a headache.
Defining the GPL Boundary in an Embedded System
Most IoT devices juggle a bootloader, a kernel, and a root filesystem. The GPL usually grabs the bootloader (U-Boot is often GPLv2+) and the Linux kernel (GPLv2). Userspace bits like the GNU C Library (LGPL) or BusyBox (GPLv2) pile on more obligations. A classic blunder is to treat the whole firmware image as one blob and ignore the separate licenses. The first engineering job is to map every binary artifact to its source package and exact license version.
I build a manifest generator right into the pipeline. With Yocto or Buildroot, the build system spits out a software bill of materials (SBOM) that lists each recipe, its version, and its license. That SBOM becomes the backbone of the compliance deliverable. If your device ships proprietary application code that links against a GPL library, you need to understand the linking exception or keep that code walled off in its own process space. I lean toward strict process separation: the proprietary control logic runs as an unprivileged user-space daemon that talks to GPL components over a documented IPC socket, never through direct linking.
Bootloader and Kernel: The Hardest Pieces
U-Boot often carries board-specific patches that never made it upstream. The GPL demands you share those modifications. I keep all U-Boot changes in a dedicated Git branch that can be bundled as a patch series against the exact upstream tag. The kernel gets the same treatment. Device tree overlays, out-of-tree drivers, and defconfig tweaks must be cleanly extractable. I run git format-patch against the stable kernel tag and drop the resulting patches into the source release. This isn’t a nice-to-have; it’s the bare minimum the license text spells out.

Designing the Build System for Reproducibility and Transparency
A GPL-compliant device has to ship the “Corresponding Source”—GPLv2 defines that as the source code needed to generate the exact binaries. That means toolchain, build scripts, and any weird environment quirks that affect the output. I standardize on a containerized build environment. A Dockerfile or Podman container that pulls the exact cross-compiler version, the needed host packages, and the build scripts lets anyone reproduce the firmware bit-for-bit. When I hand over the source archive, that container definition rides along with the source tarballs.
Buildroot makes this pretty straightforward because it generates a reproducible rootfs and a toolchain tarball. Yocto pushes even further with its extensible SDK. Whatever system you pick, write down the build command and the expected output hashes. Someone who gets your source package should be able to run a single make command and end up with the same kernel image, the same rootfs squashfs, and the same application binaries. If they can’t, your compliance has holes.
Handling Proprietary Drivers and Firmware Blobs
Lots of IoT devices lean on Wi-Fi or Bluetooth modules that need binary firmware blobs. The GPL doesn’t block distributing binary firmware; it only says the source for GPL-covered pieces must be provided. The separation just has to be obvious. I stash all binary firmware in its own partition or a dedicated directory with a clear note that these files sit outside the GPL. The kernel’s linux-firmware repository already follows that pattern. If you signed an NDA to get a radio driver, you can’t be the distributor of that driver under GPL terms if the driver itself is GPL-licensed. When I hit that wall, I either pick modules with open firmware interfaces or hash out redistribution rights with the silicon vendor before anything ships.
Physical and Online Delivery of Source Code
GPLv2 Section 3 gives three routes for source delivery: bundle a physical medium with the device, provide a written offer valid for three years, or mirror the source on a network server (with a few restrictions). For a commercial IoT product, I’d go with the written offer plus a download page. Print a URL and a QR code on the device packaging and on the device label itself. That URL has to stay live for at least three years after the last unit goes out the door. I also drop a text file onto the device’s internal storage at /usr/share/doc/gpl-sources.txt that repeats the offer and the exact commit hashes of all GPL repositories.
The download page shouldn’t demand registration, captchas, or click-through agreements. A direct HTTPS link to a compressed tar archive is the cleanest path. I organize the archive by component: a directory for U-Boot, one for the kernel, one for each GPL library, and the build container recipe. The archive needs to hold the exact source used for the firmware version stamped on the label. If you push firmware updates over-the-air, each update must carry its own source offer or stretch the existing offer to cover the new version.

Verification: The Self-Check Every Engineer Should Run
Before cutting a release, I do a “compliance build” from scratch on an air-gapped machine. I download the exact source archive I plan to publish, walk through the build instructions, and compare the output binaries against the factory image. A SHA256 checksum comparison of the kernel, modules, and shared libraries tells me whether the archive is complete. If any binary drifts, I bisect the discrepancy until the build snaps back to reproducible. This step catches missing patches, stray environment variables, or accidental pre-compiled objects. It also gives me a quiet confidence that a user or auditor can exercise their rights without leaning on me.
Scripts and Metadata
Write a small shell script that sits in the root of the source archive and automates the build. Call it build.sh. It should check for required host packages, set the cross-compilation prefix, and bail out with a clear error if something’s missing. Don’t assume the user knows your CI pipeline. That script is part of the “scripts used to control compilation and installation” the GPL talks about. The same care goes into the kernel configuration and device tree compilation commands.
Common Pitfalls and How to Avoid Them
One mistake I keep running into is the “empty source offer.” A company prints a URL that leads to a 404 or a generic contact form. That’s a straight-up violation. Another classic: providing source that matches a different firmware version. Versioning your source releases with the same Git tags as your firmware images stops that cold. Tag the firmware repo as v1.2.3-firmware and the combined source bundle as v1.2.3-sources. The link on the label points to the exact tag, not a mushy “downloads” page.
Don’t forget the toolchain, either. GPLv2 wants “the complete corresponding machine-readable source code,” which includes the compiler if the compiler itself is part of the source distribution? The FSF’s read is that major toolchain components (like GCC) must be available, but you can usually point to the upstream GCC source. I include the exact version and configuration flags of the cross-compiler in the build script and note where the matching GCC source lives. For extra safety, I mirror the GCC tarball in my source archive if I applied any patches to it.
FAQ
Do I need to release the source code for my custom IoT web interface?
It depends on the license of the pieces you used and how you link them. If your web interface is a standalone app that talks to GPL backends only through network sockets or command-line pipes, and you don’t link against GPL libraries, you’re not forced to release its source. But if you use a GPL JavaScript library in the browser client, distributing that client code might trigger the GPL’s requirements. Always check each component’s license and linking method.
What if my IoT device uses an older GPLv2 kernel and I want to keep my modifications secret?
You can’t keep modifications to GPLv2-licensed code secret once you distribute the binaries. The license requires you to hand over the source of your modified version. There’s no “trade secret” carve-out for kernel patches. If keeping certain algorithms confidential sits at the core of your business, wall them off in a proprietary userspace process that doesn’t link against GPL code and talks to the kernel through standard system calls.
Can I just put the source on GitHub and call it done?
GitHub can satisfy the written offer requirement if you keep a stable URL that points to the exact source matching the device’s firmware version. A public repository is a fair start, but you still need to tag the commit and make sure the repository stays reachable for three years after the last distribution. A GitHub release with a tarball attachment is even better because it clears up any confusion about which commit lines up with the binary.
Is it enough to provide a link to the original upstream project?
Nope. If you touched the upstream source in any way, you have to provide the modified source. Even if you made zero changes, the GPL still says you must provide the exact source that corresponds to the binaries you distributed—same version and any build configuration files. A bare link doesn’t guarantee that users grab the right version at the right point in time.