Skip to content

Running Docker Inside Apple Container

After upgrading my M3 MacBook Air to macOS 26, I finally decided to give Apple Container a try.

For a while I've been avoiding container technology on macOS for two main reasons:

  1. Most of my development work happens on remote Linux VMs.
  2. The majority of technologies that run Linux containers on macOS actually operate inside a VM powered by Apple's Virtualization Framework, which isn't ideal from a battery preservation perspective since it requires a VM running constantly.

Apple Container is particularly interesting because each container is sandboxed by a microVM. The key advantage of microVM-level isolation is that each Linux container has full privileges inside the microVM, effectively running as a complete Linux system. This enables capabilities like systemd and even Docker itself to run inside containers.

Given these capabilities, I decided to give running Docker inside a container a spin, for fun and profit.

Getting Started with Container on macOS

I installed container using Mise:

mise use -g container

# Start the container system
container system start

What's remarkable about container is that it requires no admin or sudo access at all. The installation is done entirely in user space without requiring any special permissions.

Building the Container Image with Docker and Systemd

Here is my Dockerfile:

FROM ubuntu:24.04

RUN apt-get update && apt-get install -y \
  systemd \
  curl \
  ca-certificates \
  openssh-server \
  iproute2 \
  dnsutils \
  iptables \
  && install -m 0755 -d /etc/apt/keyrings \
  && curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \
  && chmod a+r /etc/apt/keyrings/docker.asc \
  && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
  && apt-get update \
  && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
  && update-alternatives --set iptables /usr/sbin/iptables-legacy \
  && update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["/sbin/init"]

Several important aspects of this Dockerfile:

Systemd is installed to enable the container to run as a fully-fledged Linux system.

iptables legacy mode is required because Docker doesn't properly support the latest nftables implementation. The update-alternatives commands ensure the legacy version is used.

Docker installation follows the official Ubuntu installation guide.

The /sbin/init entrypoint ensures systemd starts as PID 1 when the container launches.

Using the Built-in Buildkit Backend

Apple Container includes a buildkit backend, which you can view using the container ls command:

$ container ls
ID        IMAGE                                               OS     ARCH   STATE    ADDR
buildkit  ghcr.io/apple/container-builder-shim/builder:0.6.0  linux  arm64  running  192.168.64.2

Building the image is straightforward:

container build -t ubuntu:24.04-docker .

The build command syntax mirrors docker build, except you use container build instead.

Running the Container

Launch the container using the container run command:

container run -it --name ubuntu-docker --rm ubuntu:24.04-docker

The container will start and display console logs showing systemd initialization:

systemd 255.4-1ubuntu8.10 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -BPF_FRAMEWORK -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified)
Detected virtualization container-other.
Detected architecture arm64.

Welcome to Ubuntu 24.04.3 LTS!

Queued start job for default target graphical.target.
[  OK  ] Created slice system-getty.slice - Slice /system/getty.
[  OK  ] Created slice system-modprobe.slice - Slice /system/modprobe.
[  OK  ] Created slice user.slice - User and Session Slice.

... (more lines omitted) ...

         Starting systemd-update-utmp-runlevel.service - Record Runlevel Change in UTMP...
[  OK  ] Finished systemd-update-utmp-runlevel.service - Record Runlevel Change in UTMP.

Ubuntu 24.04.3 LTS ubuntu-docker console

ubuntu-docker login:

To execute commands inside the running container:

$ container exec -it ubuntu-docker /bin/bash

root@ubuntu-docker:/# docker run -it --rm ubuntu:24.04 bash
Unable to find image 'ubuntu:24.04' locally
24.04: Pulling from library/ubuntu
7bdf644cff2e: Pull complete
Digest: sha256:fdb6c9ceb1293dcb0b7eda5df195b15303b01857d7b10f98489e7691d20aa2a1
Status: Downloaded newer image for ubuntu:24.04
root@b03bde3abfde:/# apt-get update
Get:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease [256 kB]

Docker runs successfully inside the container, demonstrating the full capabilities of the microVM isolation.

Potential Use Cases

This is my early exploration of Apple Container, and it represents an interesting piece of technology with several potential applications:

On-demand sandboxed environments for isolating LLM-generated code execution on Apple Silicon, providing security through microVM isolation.

Lightweight Linux environments on macOS without the overhead of traditional VMs, potentially serving as an alternative to tools like VMware Fusion.

It's worth noting that Apple might leverage this technology to provide a WSL-equivalent experience on macOS in the future, bringing Linux as a subsystem to macOS with more seamless integration.