Skip to content

Commit 74ace14

Browse files
Build vminitd and initfs inside the dev container (#810)
Signed-off-by: michael_crosby <michael_crosby@apple.com>
1 parent 450d44e commit 74ace14

14 files changed

Lines changed: 339 additions & 219 deletions

File tree

.github/workflows/containerization-build-template.yml

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,74 @@ on:
1515
description: Version of containerization
1616
default: test
1717

18-
jobs:
19-
buildAndTest:
18+
jobs:
19+
swift-version:
20+
name: Determine Swift version
21+
if: github.repository == 'apple/containerization'
22+
runs-on: ubuntu-24.04
23+
outputs:
24+
image: ${{ steps.version.outputs.image }}
25+
steps:
26+
- name: Checkout .swift-version
27+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
28+
with:
29+
sparse-checkout: .swift-version
30+
sparse-checkout-cone-mode: false
31+
32+
- name: Read Swift version
33+
id: version
34+
run: echo "image=swift:$(cat .swift-version)-noble" >> "$GITHUB_OUTPUT"
35+
36+
# Build the guest initfs (vminitd/vmexec compiled static-musl, then packed
37+
# into initfs.ext4 + a rootfs tar) inside a GitHub-native Swift Linux
38+
# container (Docker-backed) — NOT via apple/container, which isn't available
39+
# on GitHub runners. The macOS job below consumes these as an artifact and
40+
# creates the vminit:latest image natively with cctl. The container job is
41+
# unprivileged, so build-initfs.sh uses its `mke2fs -d` fallback (no loop
42+
# mount / no CAP_SYS_ADMIN needed).
43+
buildGuest:
44+
name: Build guest initfs
45+
if: github.repository == 'apple/containerization'
46+
needs: swift-version
47+
timeout-minutes: 30
48+
runs-on: ubuntu-24.04
49+
container: ${{ needs.swift-version.outputs.image }}
50+
steps:
51+
- name: Checkout repository
52+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
53+
with:
54+
fetch-depth: 0
55+
56+
- name: Install system dependencies
57+
run: apt-get update && apt-get install -y curl make e2fsprogs libarchive-dev libbz2-dev liblzma-dev libssl-dev
58+
59+
- name: Install Static Linux SDK
60+
run: make -C vminitd linux-sdk
61+
62+
- name: Build vminitd (static musl, aarch64)
63+
# Force aarch64 even though this runner is x86_64: the macOS buildAndTest
64+
# job boots arm64 VZ VMs, so vminitd (PID 1) must be an aarch64 binary or
65+
# the guest fails to exec and the integration boot hangs. The Static Linux
66+
# SDK cross-targets both arches from any host (same mechanism dist-x86_64
67+
# uses in reverse).
68+
run: make -C vminitd MUSL_ARCH=aarch64 BUILD_CONFIGURATION=${{ inputs.release && 'release' || 'debug' }}
69+
70+
- name: Build initfs.ext4
71+
run: ./scripts/build-initfs.sh --vminitd vminitd/bin/vminitd --vmexec vminitd/bin/vmexec --ext4 bin/initfs.ext4 --tar bin/init.rootfs.tar.gz
72+
73+
- name: Upload guest initfs
74+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
75+
with:
76+
name: initfs
77+
path: |
78+
bin/initfs.ext4
79+
bin/init.rootfs.tar.gz
80+
if-no-files-found: error
81+
82+
buildAndTest:
2083
name: Build and Test repo
2184
if: github.repository == 'apple/containerization'
85+
needs: buildGuest
2286
timeout-minutes: 60
2387
runs-on: [self-hosted, macos, tahoe, ARM64]
2488
permissions:
@@ -33,11 +97,6 @@ jobs:
3397
with:
3498
fetch-depth: 0
3599

36-
- name: Activate Swiftly
37-
run: |
38-
source ~/.swiftly/env.sh
39-
cat ~/.swiftly/env.sh
40-
41100
- name: Check formatting
42101
run: |
43102
./scripts/install-hawkeye.sh
@@ -57,11 +116,15 @@ jobs:
57116
env:
58117
BUILD_CONFIGURATION: ${{ inputs.release && 'release' || 'debug' }}
59118

60-
- name: Make vminitd image
119+
- name: Download guest initfs
120+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
121+
with:
122+
name: initfs
123+
path: bin
124+
125+
- name: Make vminit image
61126
run: |
62-
source ~/.swiftly/env.sh
63-
make -C vminitd swift linux-sdk
64-
make init
127+
make init-image
65128
env:
66129
BUILD_CONFIGURATION: ${{ inputs.release && 'release' || 'debug' }}
67130

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Build / Test / Format
66

7-
The project is built via `make`, not directly with `swift build`. Two Swift packages live in this repo: the root package (Containerization libraries + `cctl` + macOS-only integration binary) and `vminitd/` (the Linux guest init system, cross-compiled with the Static Linux SDK).
7+
The project is built via `make`, not directly with `swift build`. Two Swift packages live in this repo: the root package (Containerization libraries + `cctl` + macOS-only integration binary) and `vminitd/` (the Linux guest init system, compiled as a static musl binary inside the Linux dev container via the apple/`container` CLI — see `make vminitd`).
88

99
- `make all` — build everything (`containerization` + `vminitd` + `init.ext4` rootfs in `bin/`). Default `BUILD_CONFIGURATION=debug`; pass `release` (or use `make release`) for optimized builds.
1010
- `make containerization` — build just the host-side Swift package (skips vminitd).
11-
- `make vminitd` — build vminitd / vmexec only. By default uses `LIBC=musl` via the Static Linux SDK; `make linux-build LIBC=glibc` builds via a Linux dev container.
11+
- `make vminitd` — build vminitd / vmexec only. On macOS this runs `swift build --swift-sdk …-swift-linux-musl` *inside the Linux dev container* via the `container` CLI (the cloud-hypervisor build model), producing static musl binaries at `vminitd/bin/`; no host Swiftly/SDK needed. `make linux-build LIBC=glibc` builds via a Linux dev container.
1212
- `make test` — unit tests with code coverage. `make coverage` regenerates the coverage report.
1313
- `make integration` — runs `bin/containerization-integration`. Requires an in-repo kernel under `bin/` (`bin/vmlinux-arm64` on arm64, `bin/vmlinuz-x86_64` or `bin/vmlinux-x86_64` on x86_64); if absent, run `make fetch-default-kernel` to download the Kata-provided kernel for the host arch.
1414
- Single test: `swift test --filter ContainerizationOCITests.ReferenceTests/testParsing` (Swift Testing / XCTest filter syntax). Targets are listed in `Package.swift`.
@@ -22,7 +22,7 @@ The project is built via `make`, not directly with `swift build`. Two Swift pack
2222
- `make check` — formatting + license-header lint (this is what the pre-commit hook runs). Uses `.swift-format-nolint` for stricter linting.
2323
- `make pre-commit` — installs `scripts/pre-commit.fmt` as a git pre-commit hook.
2424
- `make protos` — regenerates `Sources/Containerization/SandboxContext/SandboxContext.{pb,grpc}.swift` from the `.proto`. Touch this whenever the proto changes; never hand-edit the generated files.
25-
- `make cross-prep`installs Swiftly, the pinned Swift toolchain (see `.swift-version`), and the Static Linux SDK. Run once before the first build.
25+
- `make init` / `make init-image``init` compiles the guest and builds `bin/initfs.ext4` (+ a rootfs tar) inside the dev container via `scripts/build-initfs.sh` (mkfs + loop mount, with a `mke2fs -d` fallback), then `init-image` creates the `vminit:latest` OCI image from the tar with the native `cctl` (`cctl rootfs create --rootfs <tar> --image vminit:latest`). CI splits these: a Linux container job builds the initfs artifact, the macOS job runs `init-image`. Building the guest on macOS requires the apple/`container` CLI — there is no host Swiftly / Static Linux SDK setup step anymore.
2626

2727
`WARNINGS_AS_ERRORS=true` is the default for both packages. Don't disable it casually — CI builds with it on.
2828

@@ -88,4 +88,4 @@ These are independently consumable Swift modules. Keep their dependencies narrow
8888

8989
## Requirements
9090

91-
Apple silicon Mac, macOS 26, Xcode 26. Swift toolchain version is pinned in `.swift-version` (currently `6.3.0`) and installed via Swiftly during `make cross-prep`. Older macOS releases are not supported.
91+
Apple silicon Mac, macOS 26, Xcode 26. The host-side build uses Xcode's Swift toolchain (`/usr/bin/swift`); the Linux guest is built inside the dev container, so the apple/`container` CLI is required (see the README). The pinned Swift version (`.swift-version`, currently `6.3.0`) tags the dev image and the CI Swift Linux container. Older macOS releases are not supported.

Makefile

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -294,26 +294,60 @@ ifeq ($(UNAME_S),Darwin)
294294
@codesign --force --sign - --timestamp=none --entitlements=signing/vz.entitlements bin/containerization-integration
295295
endif
296296

297+
# Shell fragments run inside the Linux dev container (see linux_run). Kept as
298+
# variables so `vminitd` (compile only) and `init` (compile + build the initfs
299+
# in a single container run) don't duplicate the command.
300+
VMINITD_BUILD_CMD = make -C vminitd BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS)
301+
INITFS_BUILD_CMD = ./scripts/build-initfs.sh --vminitd vminitd/bin/vminitd --vmexec vminitd/bin/vmexec --ext4 bin/initfs.ext4 --tar bin/init.rootfs.tar.gz
302+
297303
.PHONY: init
298-
init: containerization vminitd
299-
@echo Creating init.ext4...
300-
@rm -f bin/init.rootfs.tar.gz bin/init.block bin/initfs.ext4
304+
ifeq ($(UNAME_S),Darwin)
305+
# Compile the guest and build the initfs (ext4 + rootfs tar) in a single dev
306+
# container run — where mkfs/loop-mount live — then create the vminit:latest
307+
# OCI image natively from the tar. The container's output is the finished
308+
# initfs, not raw binaries.
309+
init: containerization
310+
@mkdir -p ./bin
311+
$(call linux_run,$(VMINITD_BUILD_CMD) && $(INITFS_BUILD_CMD))
312+
@"$(MAKE)" init-image
313+
else
314+
init: containerization
315+
@mkdir -p ./bin
316+
@$(VMINITD_BUILD_CMD)
317+
@$(INITFS_BUILD_CMD)
318+
@"$(MAKE)" init-image
319+
endif
320+
321+
# Create the vminit:latest OCI image from the container-built rootfs tar, using
322+
# the native cctl. Split out from `init` so CI can create the image after
323+
# downloading the initfs artifact built by the Linux container job — no
324+
# apple/container needed on the macOS runner. The integration suite and the
325+
# release ghcr push consume this image.
326+
.PHONY: init-image
327+
init-image:
328+
@echo Creating vminit:latest image...
329+
@rm -f bin/init.block
301330
@./bin/cctl rootfs create \
302-
--vminitd vminitd/bin/vminitd \
303-
--vmexec vminitd/bin/vmexec \
304-
--ext4 ./bin/initfs.ext4 \
305-
--label org.opencontainers.image.source=https://github.com/apple/containerization \
306331
--image vminit:latest \
332+
--label org.opencontainers.image.source=https://github.com/apple/containerization \
307333
bin/init.rootfs.tar.gz
308334

309-
.PHONY: cross-prep
310-
cross-prep:
311-
@"$(MAKE)" -C vminitd cross-prep
312-
313335
.PHONY: vminitd
336+
ifeq ($(UNAME_S),Darwin)
337+
# On macOS, vminitd/vmexec are static musl Linux binaries. Rather than
338+
# cross-compiling on the host (which used to require Swiftly + the Static
339+
# Linux SDK via `make cross-prep`), build them inside the Linux dev container
340+
# via `linux_run` — the same model `build-cloud-hypervisor` uses. The dev
341+
# image bakes in the Static Linux SDK, and the /workspace mount makes the
342+
# resulting binaries visible on the host at vminitd/bin/.
314343
vminitd:
315344
@mkdir -p ./bin
316-
@"$(MAKE)" -C vminitd BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS)
345+
$(call linux_run,$(VMINITD_BUILD_CMD))
346+
else
347+
vminitd:
348+
@mkdir -p ./bin
349+
@$(VMINITD_BUILD_CMD)
350+
endif
317351

318352
.PHONY: update-libarchive-source
319353
update-libarchive-source:

README.md

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,31 +100,19 @@ Set the active developer directory to the installed Xcode (replace `<PATH_TO_XCO
100100
sudo xcode-select -s <PATH_TO_XCODE>
101101
```
102102

103-
Install [Swiftly](https://github.com/swiftlang/swiftly), [Swift](https://www.swift.org), and [Static Linux SDK](https://www.swift.org/documentation/articles/static-linux-getting-started.html):
103+
The Linux guest init (`vminitd`/`vmexec`) is compiled as a static binary
104+
*inside a Linux container* rather than cross-compiled on your Mac, so no Swift
105+
toolchain, Swiftly, or Static Linux SDK setup is required on the host. Install
106+
the [`container`](https://github.com/apple/container) CLI, which the build uses
107+
to compile the guest:
104108

105109
```bash
106-
make cross-prep
110+
# Install per https://github.com/apple/container, then verify it is on PATH:
111+
container --version
107112
```
108113

109-
If you use a custom terminal application, you may need to move this command from `.zprofile` to `.zshrc` (replace `<USERNAME>`):
110-
111-
```bash
112-
# Added by swiftly
113-
. "/Users/<USERNAME>/.swiftly/env.sh"
114-
```
115-
116-
Restart the terminal application. Ensure this command returns `/Users/<USERNAME>/.swiftly/bin/swift` (replace `<USERNAME>`):
117-
118-
```bash
119-
which swift
120-
```
121-
122-
If you've installed or used a Static Linux SDK previously, you may need to remove older SDK versions from the system (replace `<SDK-ID>`):
123-
124-
```bash
125-
swift sdk list
126-
swift sdk remove <SDK-ID>
127-
```
114+
The first build automatically builds the Linux dev image used to compile the
115+
guest, which can take a few minutes.
128116

129117
## Build the package
130118

0 commit comments

Comments
 (0)