eBPF and WASM - Better Together
In February 2025, Inspektor Gadget moved from built-in gadgets to OCI-image-based gadgets.
From the outside, the model looks almost too simple: ship an eBPF program that captures kernel events and (optionally) add a Wasm module to process that data.
But that's quite surprising, since if you've ever built an eBPF application before, you know there is a lot more than just the kernel program—and it is not obvious at all where Wasm even fits into the stack.

While Inspektor Gadget has many other features, the key question is fairly straightforward - how can one only focus on writing the eBPF kernel program and the data processing logic using Wasm and the rest just works?
In this lab, we'll build that split from scratch.
The eBPF Boilerplate Problem
In general, every eBPF application has the same structure:
- Kernel-space program — hooks into the kernel, captures events, and either acts on them directly or sends them to user space through an eBPF map.
- User-space program — loads the compiled kernel program, attaches it, reads events from the eBPF map, and runs your application logic.

💡 If you've followed From Zero to Your First eBPF Program, that split should feel familiar: hello.c in the kernel, main.go in user space.
This setup is pretty flexible, but it repeats the same work in almost every project:
- Compiling the eBPF program and generating language bindings
- Loading the kernel program and attaching it to the right kernel hook
- Creating (and pinning) eBPF maps
- Defining event structs shared between kernel and user space
- Shutting everything down cleanly on exit
Most of that code has nothing to do with what you want to observe—only with how you get that data out of the kernel.
And even worse, each project tends to structure this logic differently.
So the gem of every project—like "capture and log binaries executed by root"—lives inside the project's setup and cannot be reused elsewhere without rewriting the surrounding boilerplate code.
With this in mind, projects like Inspektor Gadget have shifted toward shipping eBPF programs as OCI artifacts—portable bundles you can pull, version, and run like container images—so developers can focus on application logic and avoid the boilerplate that usually comes with eBPF development today.
Never used Inspektor Gadget before?
Let's run one of their gadgets, using:
sudo ig run trace_exec:latest --host
💡 --host in this case shows "executed binaries" events from all processes, not just containers. We need it here because this playground has no container runtime and the gadget would fail without it.
Now, open a second terminal tab, trigger a few execve calls (e.g. ls, cat /etc/os-release), and watch the output.
And if you peek inside that OCI-image-based gadget—it's just an eBPF program and a Wasm module (written in Go) bundled together:
sudo ig image inspect trace_exec:latest \
--extra-info oci.manifest --jsonpath '.layers[*].mediaType'
[
"application/vnd.gadget.ebpf.program.v1+binary",
"application/vnd.gadget.wasm.program.v1+binary"
]
But how is that even possible?
Materials by an Independent Author
Extra content pack required to access this material
