How to Create Custom Init Images for Apple container: Advanced Container Boot Customization
Purpose
This post demonstrates how to create custom init images for Apple container — replacing the default vminitd with a custom one that runs VM-level logic before the OCI container starts.
What Are Init Images
Each Apple container boots a lightweight VM managed by vminitd, the built-in init system. The --init-image flag lets me replace that default init with a custom image, adding arbitrary logic inside the VM before my application starts.
┌─────────────────────────────────────────────┐│ Default Boot Custom Boot ││ ││ VM boots → vminitd → container ││ (no extension point) ││ ││ vs ││ ││ VM boots → custom-wrapper → vminitd → ││ (eBPF, logging, ││ instrumentation) container │└─────────────────────────────────────────────┘When to Use Custom Init Images
- eBPF network filters: run VM-level packet inspection before the container starts
- Logging agents: capture all VM output including early boot messages
- Boot debugging: instrument the boot sequence for performance analysis
- Secret injection: mount secrets at the VM level before the container process starts
- Custom hardware init: initialize special devices visible from the VM
For simpler needs, the built-in --init flag provides a lightweight PID 1 that handles signal forwarding and zombie reaping — no custom image required.
Building a Custom Init Image
Step 1: Write a Wrapper
Create a Go program that logs a message then hands off to vminitd:
package main
import ( "os" "syscall")
func main() { // Write to kernel log f, _ := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0) f.WriteString("custom-init: boot started\n") f.Close()
// Hand off to the real vminitd syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())}Build for the target architecture:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o init-wrapper main.goStep 2: Create the Dockerfile
FROM ghcr.io/apple/containerization/vminit:0.33.3 AS base
FROM scratchCOPY --from=base /sbin/vminitd /sbin/vminitd.realCOPY init-wrapper /sbin/vminitd
Step 3: Build the Init Image
container build -t local/custom-init:latest --file Dockerfile .Step 4: Run with Custom Init
container run --init-image local/custom-init:latest --name my-app alpine:latestStep 5: Verify
container logs --boot my-appcustom-init: boot started...The Built-in Alternative
For most use cases, the --init flag is simpler:
container run --init --name my-app alpine:latestThis runs a lightweight init that handles:
- Signal forwarding (Ctrl+C reaches the container process)
- Zombie process reaping
- No custom image needed
Comparison
| Approach | Complexity | Use Case |
|---|---|---|
--init | None | Basic signal forwarding, zombie reaping |
--init-image | Build custom image | eBPF, logging agents, boot debugging |
Summary
In this post, I showed how to create custom init images for Apple container by wrapping vminitd with a custom binary. The key point is that --init-image enables VM-level customization for advanced use cases, while --init handles the common signal and zombie management needs without any extra work.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments