After getting Apple’s container running, the thing I actually wanted was a normal Ubuntu box to poke around in. That took one more step than I expected: systemd.
A container runs whatever its CMD is as PID 1. The stock ubuntu:26.04 image doesn’t ship an init, so you boot into a bare shell and not much else. If you want services, units, and a machine that behaves like a real Ubuntu install, systemd has to be the thing running as init. So you build your own image.
Here’s the Dockerfile. Most of it is the well-worn systemd-in-a-container cleanup: install systemd, then delete the units that make no sense inside a container so it doesn’t sit there trying to start hardware that isn’t there.
FROM ubuntu:26.04
ENV container container
RUN apt update -y && apt upgrade -y && apt install -y systemd init
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]Build it, create a machine from it, and run:
❯ container build -t local/devel -f devel.dockerfile
[+] Building 356.1s (7/7) FINISHED
=> [resolver] fetching image docker.io/library/ubuntu:26.04
=> [internal] load build definition from Dockerfile
=> [linux/arm64 1/3] RUN apt update -y && apt upgrade -y && apt install -y systemd init
=> [linux/arm64 2/3] RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do ... )
=> exporting to oci image format
local/devel:latest
❯ container machine create --name devel --set-default local/devel
devel
❯ container machine run
chris.houdeshell@devel:/Users/chris.houdeshell$That last line is the Ubuntu shell. You’re in.
What the flags are doing, quickly:
container build -t local/devel -f devel.dockerfile:-ttags the image aslocal/devel;-fpoints at the Dockerfile by name, since I didn’t name itDockerfile.container machine create --name devel --set-default local/devel: builds a VM nameddevelfrom that image.--set-defaultmakes it the machine everything else targets, so I don’t have to spell out the name on every command.container machine run: boots the default machine and drops me into its shell.
One thing worth knowing: that build ran just shy of six minutes. The tool is young and the build path isn’t quick yet, so don’t assume it hung.