Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 69 additions & 11 deletions .github/workflows/containerization-build-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,69 @@ on:
description: Version of containerization
default: test

jobs:
buildAndTest:
jobs:
swift-version:
name: Determine Swift version
if: github.repository == 'apple/containerization'
runs-on: ubuntu-24.04
outputs:
image: ${{ steps.version.outputs.image }}
steps:
- name: Checkout .swift-version
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
sparse-checkout: .swift-version
sparse-checkout-cone-mode: false

- name: Read Swift version
id: version
run: echo "image=swift:$(cat .swift-version)-noble" >> "$GITHUB_OUTPUT"

# Build the guest initfs (vminitd/vmexec compiled static-musl, then packed
# into initfs.ext4 + a rootfs tar) inside a GitHub-native Swift Linux
# container (Docker-backed) — NOT via apple/container, which isn't available
# on GitHub runners. The macOS job below consumes these as an artifact and
# creates the vminit:latest image natively with cctl. The container job is
# unprivileged, so build-initfs.sh uses its `mke2fs -d` fallback (no loop
# mount / no CAP_SYS_ADMIN needed).
buildGuest:
name: Build guest initfs
if: github.repository == 'apple/containerization'
needs: swift-version
timeout-minutes: 30
runs-on: ubuntu-24.04
container: ${{ needs.swift-version.outputs.image }}
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
fetch-depth: 0

- name: Install system dependencies
run: apt-get update && apt-get install -y curl make e2fsprogs libarchive-dev libbz2-dev liblzma-dev libssl-dev

- name: Install Static Linux SDK
run: make -C vminitd linux-sdk

- name: Build vminitd (static musl)
run: make -C vminitd BUILD_CONFIGURATION=${{ inputs.release && 'release' || 'debug' }}

- name: Build initfs.ext4
run: ./scripts/build-initfs.sh --vminitd vminitd/bin/vminitd --vmexec vminitd/bin/vmexec --ext4 bin/initfs.ext4 --tar bin/init.rootfs.tar.gz

- name: Upload guest initfs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: initfs
path: |
bin/initfs.ext4
bin/init.rootfs.tar.gz
if-no-files-found: error

buildAndTest:
name: Build and Test repo
if: github.repository == 'apple/containerization'
needs: buildGuest
timeout-minutes: 60
runs-on: [self-hosted, macos, tahoe, ARM64]
permissions:
Expand All @@ -33,11 +92,6 @@ jobs:
with:
fetch-depth: 0

- name: Activate Swiftly
run: |
source ~/.swiftly/env.sh
cat ~/.swiftly/env.sh

- name: Check formatting
run: |
./scripts/install-hawkeye.sh
Expand All @@ -57,11 +111,15 @@ jobs:
env:
BUILD_CONFIGURATION: ${{ inputs.release && 'release' || 'debug' }}

- name: Make vminitd image
- name: Download guest initfs
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: initfs
path: bin

- name: Make vminit image
run: |
source ~/.swiftly/env.sh
make -C vminitd swift linux-sdk
make init
make init-image
env:
BUILD_CONFIGURATION: ${{ inputs.release && 'release' || 'debug' }}

Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Build / Test / Format

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).
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`).

- `make all` — build everything (`containerization` + `vminitd` + `init.ext4` rootfs in `bin/`). Default `BUILD_CONFIGURATION=debug`; pass `release` (or use `make release`) for optimized builds.
- `make containerization` — build just the host-side Swift package (skips vminitd).
- `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.
- `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.
- `make test` — unit tests with code coverage. `make coverage` regenerates the coverage report.
- `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.
- Single test: `swift test --filter ContainerizationOCITests.ReferenceTests/testParsing` (Swift Testing / XCTest filter syntax). Targets are listed in `Package.swift`.
Expand All @@ -22,7 +22,7 @@ The project is built via `make`, not directly with `swift build`. Two Swift pack
- `make check` — formatting + license-header lint (this is what the pre-commit hook runs). Uses `.swift-format-nolint` for stricter linting.
- `make pre-commit` — installs `scripts/pre-commit.fmt` as a git pre-commit hook.
- `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.
- `make cross-prep` — installs Swiftly, the pinned Swift toolchain (see `.swift-version`), and the Static Linux SDK. Run once before the first build.
- `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.

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

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

## Requirements

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.
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.
58 changes: 46 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -288,26 +288,60 @@ ifeq ($(UNAME_S),Darwin)
@codesign --force --sign - --timestamp=none --entitlements=signing/vz.entitlements bin/containerization-integration
endif

# Shell fragments run inside the Linux dev container (see linux_run). Kept as
# variables so `vminitd` (compile only) and `init` (compile + build the initfs
# in a single container run) don't duplicate the command.
VMINITD_BUILD_CMD = make -C vminitd BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS)
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

.PHONY: init
init: containerization vminitd
@echo Creating init.ext4...
@rm -f bin/init.rootfs.tar.gz bin/init.block bin/initfs.ext4
ifeq ($(UNAME_S),Darwin)
# Compile the guest and build the initfs (ext4 + rootfs tar) in a single dev
# container run — where mkfs/loop-mount live — then create the vminit:latest
# OCI image natively from the tar. The container's output is the finished
# initfs, not raw binaries.
init: containerization
@mkdir -p ./bin
$(call linux_run,$(VMINITD_BUILD_CMD) && $(INITFS_BUILD_CMD))
@"$(MAKE)" init-image
else
init: containerization
@mkdir -p ./bin
@$(VMINITD_BUILD_CMD)
@$(INITFS_BUILD_CMD)
@"$(MAKE)" init-image
endif

# Create the vminit:latest OCI image from the container-built rootfs tar, using
# the native cctl. Split out from `init` so CI can create the image after
# downloading the initfs artifact built by the Linux container job — no
# apple/container needed on the macOS runner. The integration suite and the
# release ghcr push consume this image.
.PHONY: init-image
init-image:
@echo Creating vminit:latest image...
@rm -f bin/init.block
@./bin/cctl rootfs create \
--vminitd vminitd/bin/vminitd \
--vmexec vminitd/bin/vmexec \
--ext4 ./bin/initfs.ext4 \
--label org.opencontainers.image.source=https://github.com/apple/containerization \
--image vminit:latest \
--label org.opencontainers.image.source=https://github.com/apple/containerization \
bin/init.rootfs.tar.gz

.PHONY: cross-prep
cross-prep:
@"$(MAKE)" -C vminitd cross-prep

.PHONY: vminitd
ifeq ($(UNAME_S),Darwin)
# On macOS, vminitd/vmexec are static musl Linux binaries. Rather than
# cross-compiling on the host (which used to require Swiftly + the Static
# Linux SDK via `make cross-prep`), build them inside the Linux dev container
# via `linux_run` — the same model `build-cloud-hypervisor` uses. The dev
# image bakes in the Static Linux SDK, and the /workspace mount makes the
# resulting binaries visible on the host at vminitd/bin/.
vminitd:
@mkdir -p ./bin
@"$(MAKE)" -C vminitd BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) WARNINGS_AS_ERRORS=$(WARNINGS_AS_ERRORS)
$(call linux_run,$(VMINITD_BUILD_CMD))
else
vminitd:
@mkdir -p ./bin
@$(VMINITD_BUILD_CMD)
endif

.PHONY: update-libarchive-source
update-libarchive-source:
Expand Down
30 changes: 9 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,19 @@ Set the active developer directory to the installed Xcode (replace `<PATH_TO_XCO
sudo xcode-select -s <PATH_TO_XCODE>
```

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):
The Linux guest init (`vminitd`/`vmexec`) is compiled as a static binary
*inside a Linux container* rather than cross-compiled on your Mac, so no Swift
toolchain, Swiftly, or Static Linux SDK setup is required on the host. Install
the [`container`](https://github.com/apple/container) CLI, which the build uses
to compile the guest:

```bash
make cross-prep
# Install per https://github.com/apple/container, then verify it is on PATH:
container --version
```

If you use a custom terminal application, you may need to move this command from `.zprofile` to `.zshrc` (replace `<USERNAME>`):

```bash
# Added by swiftly
. "/Users/<USERNAME>/.swiftly/env.sh"
```

Restart the terminal application. Ensure this command returns `/Users/<USERNAME>/.swiftly/bin/swift` (replace `<USERNAME>`):

```bash
which swift
```

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>`):

```bash
swift sdk list
swift sdk remove <SDK-ID>
```
The first build automatically builds the Linux dev image used to compile the
guest, which can take a few minutes.

## Build the package

Expand Down
Loading
Loading