# How to Use Custom Init Images to Run VM-Level Daemons Before Container Start in Apple Container

> Learn to run VM-level daemons before containers start in Apple Container using custom init images with the --init-image flag. Execute custom logic before main processes.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-06-21

---

**You can execute VM-level daemons before your container's main process starts by supplying a custom init image via the `--init-image` flag, which replaces the default `vminitd` binary with a wrapper that runs your custom logic before delegating to the original init system.**

Apple Container launches each workload inside a lightweight virtual machine (VM) that boots using a default init image containing the `vminitd` binary. By creating and specifying **custom init images**, you can inject arbitrary code—such as eBPF filters, kernel log emitters, or system daemons—into the VM boot sequence before the OCI container's PID 1 launches.

## Understanding the Init Image Architecture

By default, Apple Container boots VMs using the **`vminitd`** binary packaged in the `vminit` image (`ghcr.io/apple/containerization/vminit:<tag>`). This binary acts as the VM's init system, responsible for setting up the container environment and launching the actual workload.

The **`--init-image`** flag allows you to substitute this default filesystem image with your own. When you provide a custom init image, the VM boots using your specified filesystem, executing your custom logic before the standard container initialization proceeds. This enables you to run arbitrary code at the VM level—such as starting background daemons, loading kernel modules, or configuring network interfaces—before the container's entrypoint executes.

## Building a Custom Init Image

To create a functional custom init image, you must preserve the original `vminitd` functionality while inserting your own initialization logic. The standard approach involves creating a wrapper binary that executes your custom code and then delegates to the real `vminitd` binary.

### Step 1 – Create the Wrapper Binary

Write a wrapper program that performs your VM-level initialization tasks and then executes the original `vminitd` binary. The following Go example writes a message to the kernel log before handing control to the standard init process:

```go
// wrapper.go
package main

import (
    "os"
    "syscall"
)

func main() {
    // Example: write a message to the kernel log
    kmsg, err := os.OpenFile("/dev/kmsg", os.O_WRONLY, 0)
    if err == nil {
        kmsg.WriteString("<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\n")
        kmsg.Close()
    }

    // Now launch the real vminitd
    err = syscall.Exec("/sbin/vminitd.real", os.Args, os.Environ())
    if err != nil {
        os.Exit(1)
    }
}

```

Build the binary for your target architecture (for example, arm64):

```bash
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go

```

### Step 2 – Construct the Containerfile

Create a Containerfile that preserves the original `vminitd` binary as `/sbin/vminitd.real` while installing your wrapper as `/sbin/vminitd`. Use the same version tag that your container runtime expects:

```dockerfile

# Use the same version as the container runtime expects

FROM ghcr.io/apple/containerization/vminit:0.34.0 AS base

FROM ghcr.io/apple/containerization/vminit:0.34.0
COPY --from=base /sbin/vminitd /sbin/vminitd.real   # preserve the original

COPY wrapper /sbin/vminitd                         # replace with our wrapper

```

### Step 3 – Build the Custom Init Image

Build the image using the Apple Container CLI:

```bash
container build -t local/custom-init:latest .

```

## Running Containers with Custom Init Images

Once you have built your custom init image, you can deploy it using the `--init-image` flag to inject your VM-level daemons into the boot sequence.

### Using the --init-image Flag

Invoke `container run` with the `--init-image` parameter pointing to your custom image:

```bash
container run \
    --name my-container \
    --init-image local/custom-init:latest \
    alpine:latest \
    echo "hello"

```

The VM boots using your wrapper as the init process, executes your custom code, and then delegates to the standard `vminitd.real` to complete the normal container initialization.

### Inspecting Boot Logs

Verify that your custom init logic executed by examining the VM boot logs:

```bash
container logs --boot my-container | grep custom-init

```

You should see output confirming your daemon ran:

```text
[    0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===

```

## Setting Default Init Images

You can configure a default custom init image for all containers by modifying the system configuration file. According to [`docs/container-system-config.md`](https://github.com/apple/container/blob/main/docs/container-system-config.md), add a `[vminit]` section to specify the default init image path, eliminating the need to pass `--init-image` with every command.

## Summary

- **Custom init images** replace the default `vminitd` binary in Apple Container's lightweight VMs, allowing you to run code before the container's PID 1 starts.
- The wrapper pattern requires preserving the original binary as `/sbin/vminitd.real` and installing your wrapper at `/sbin/vminitd`.
- Build custom init images using standard `container build` commands with a Containerfile based on the official `vminit` image.
- Use the `--init-image` flag when running containers to specify your custom init image.
- Verify execution using `container logs --boot` to inspect kernel messages and boot output.

## Frequently Asked Questions

### What is the default init image used by Apple Container?

Apple Container uses the `ghcr.io/apple/containerization/vminit:<tag>` image by default, which contains the `vminitd` binary that serves as the VM's init process. This binary is responsible for initializing the container environment before launching the workload.

### Can I run multiple daemons in a custom init image?

Yes. Your wrapper binary can start multiple background processes—such as logging agents, eBPF filters, or networking daemons—before executing `vminitd.real`. Ensure these daemons either run in the background or fork properly to avoid blocking the init handoff.

### How do I debug boot issues with custom init images?

Use the `container logs --boot <container-name>` command to inspect the VM boot logs. This output includes kernel messages and any output from your custom init wrapper, allowing you to trace failures in the initialization sequence before the main container process starts.

### Is it possible to set a custom init image as the default?

Yes. According to [`docs/container-system-config.md`](https://github.com/apple/container/blob/main/docs/container-system-config.md), you can configure a default init image by adding a `[vminit]` section to the system configuration file. This setting applies the custom init image to all containers without requiring the `--init-image` flag on each run command.