Apple Containers

A real docker replacement?

Apple Containers seems to be all the hype right now. (besides all things AI) Against a common misconception, Docker is actually still free. Yes, you can pay the 5EUR but as a private user you are free to use Docker, including Docker Desktop. That being said, especially on a Mac, Docker still isn’t natively running, so you run Docker desktop, and then that under the hood basically starts a small Linux VM and in there your actual containers run. So let’s find out if Apple Containers actually let you run things natively, if the performance is better, how the usability is and so on.

Just so we’re clear: this is a) written by me and just me, without some AI and b) not a code walk or a product pitch. I’m just trying it out and take you along on the way.

Installation

Installation is fairly simple. You can just go to the Apple Container repository and then get the installer from the release page. The installer is available signed and unsigned. The signed variant lets you click through the installation process, and what I always like, asks you nicely if it should move itself to the bin once you’re done.

If you want to get started right away, you’ll find out that you still have to “start the system”:

$ container ps
Error: Plugins are unavailable. Start the container system services and retry:

    container system start

Check to see that the plugin exists under:
  - /usr/local/libexec/container-plugins/ps
  - /usr/local/libexec/container/plugins/ps

Usage: container [--debug] <subcommand>
  See 'container --help' for more information.

Let’s do that.

We are asked if we want to install the default kernel. What for? No one knows, but we will find out in a bit. Interestingly, the default is coming from Kata containers:

$ container system start
Launching container-apiserver...
Testing access to container-apiserver...
Verifying machine API server is running...
No default kernel configured.
Install the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.28.0/kata-static-3.28.0-arm64.tar.zst]? [Y/n]: Y
Installing kernel...

Usage

Now that we’re ready, let’s see if anything is running:


$ container ps
Error: Plugin 'container-ps' not found.

- If system services are not running, start them with: container system start
- If the plugin isn't installed, ensure it exists under:

Check to see that the plugin exists under:
  - /usr/local/libexec/container-plugins/ps
  - /usr/local/libexec/container/plugins/ps

Usage: container [--debug] <subcommand>
  See 'container --help' for more information.
$ container system start
Launching container-apiserver...
Testing access to container-apiserver...
Verifying machine API server is running...

Soooo no containers? But at least it’s running. I think now we’re going to the docs, and basically it’s just the wrong command. Let’s try the correct one:

$ container list --all
ID  IMAGE  OS  ARCH  STATE  IP  CPUS  MEMORY  STARTED

Great, that works, but it’s honestly already annoying. If something is a replacement for something else, I do somewhat expect it to behave the same way. That is the case, for example, with Podman, where you can literally just alias docker=podman and use podman (with some exceptions, but that’s for a different blog).

Maybe I was just a bit unlucky here because the other basic commands seem to actually be the same:

  create                  Create a new container
  delete, rm              Delete one or more containers
  exec                    Run a new command in a running container
  inspect                 Display information about one or more containers
  kill                    Kill one or more running containers
  list, ls                List containers
  logs                    Fetch container stdio or boot logs
  run                     Run a container
  start                   Start a container
  stop                    Stop one or more running containers

Performance and user experience

Now that we know how to use it, let’s run some containers.

We can just start off with a simple Ubuntu container:

$ container run -ti --rm ubuntu /bin/bash
root@be5a98e3-fc95-47ba-b337-9fc436706845:/# whoami
root

That went smoothly. Let’s actually try and do something useful. We will build a simple go app that just responds to a get request and then stick that in a container and respectively expose the port then, so we can reach the app from outside the container.

First things first: the go app. Since this will not really run anywhere, we can keep it simple and use net/http instead of bringing in gin and everything. We will also keep everything in the main here:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, World")
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

If you’re not super familiar with go, that’s no big deal. Let me explain: In the main function we define a handler function and the respective URL context. We define the handler function inline instead of moving it outside the main and basically say that we want to answer a request that we receive on / with “Hello, World”. Then we actually start the server and tell it to listen on port 8080. That’s wrapped in a logger, so that if there is any issue when running the server, we log and exit with statuscode 1.

We can validate that it’s doing what it’s supposed to on the command line. In one terminal panel we run the go app like so:

$ go run main.go 

Nothing more to do since we don’t accept any input and just hardcoded the listen port.

To verify that it’s doing what it should, we can just curl like so:

$ curl localhost:8080
Hello, World

So far so good. Now it just has to go in a container. For that we are going to write ourselves a Dockerfile. Since it’s very little extra effort, we’ll use a nice multi-stage Dockerfile. That also allows us to verify right away that this also works with Apple Containers. In case you haven’t used one yet, the idea is that you define two or more “stages” for your Dockerfile, where each of them is basically a complete Dockerfile and you can use, for example, artifacts built in one stage in the next one.

Let’s build our example and then review it:

FROM golang AS builder

WORKDIR /app
COPY . .

RUN CGO_ENABLED=0 go build .

FROM scratch

WORKDIR /app

COPY --from=builder /app/pingServer .

ENTRYPOINT ["/app/pingServer"]

As you can see, we have two FROM statements. This is where each of our definitions starts. The first one, we even “named” builder. That’s nice, so we can reference it in the second one. You think of these as resembling a stage. In the first stage, we copy all files from the current directory to the image. Normally you should be a bit more selective, but in this case I really only have my main.go and its respective go modules file in the directory, so I can do that. Then we build the go app and set CGO_ENABLED=0. Since this is not post doesn’t revolve around go, let’s just say it’s necessary to run self-contained.

In the second stage we use scratch as the base, so pretty much nothing, then we make a directory and switch to it. Next, we import the binary that we built in the first stage and then set it as the entry point.

Let’s build it:

$ container build -t demo .
[+] Building 21.0s (11/11) FINISHED
 => [resolver] fetching image...docker.io/library/golang                                                                                                                                                                                                                                                                                                               0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 210B                                                                                                                                                                                                                                                                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                                                                        0.0s
 => oci-layout://docker.io/library/golang@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                                      5.4s
 => => resolve docker.io/library/golang@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                                        0.0s
 => => sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747 126B / 126B                                                                                                                                                                                                                                                                             0.0s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                                                                                                                                                               0.0s
 => => sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15 64.18MB / 64.18MB                                                                                                                                                                                                                                                                       1.5s
 => => sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938 98.42MB / 98.42MB                                                                                                                                                                                                                                                                       1.7s
 => => sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1 67.60MB / 67.60MB                                                                                                                                                                                                                                                                       1.5s
 => => sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7 25.03MB / 25.03MB                                                                                                                                                                                                                                                                       0.6s
 => => sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563 49.67MB / 49.67MB                                                                                                                                                                                                                                                                       1.0s
 => => extracting sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563                                                                                                                                                                                                                                                                              0.6s
 => => extracting sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7                                                                                                                                                                                                                                                                              0.3s
 => => extracting sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1                                                                                                                                                                                                                                                                              0.8s
 => => extracting sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938                                                                                                                                                                                                                                                                              0.9s
 => => extracting sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15                                                                                                                                                                                                                                                                              1.1s
 => => extracting sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747                                                                                                                                                                                                                                                                              0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64/v8 stage-1 1/2] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.0s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                      0.1s
 => => transferring context: 7.94MB                                                                                                                                                                                                                                                                                                                                    0.1s
 => [linux/arm64/v8 builder 1/4] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.4s
 => [linux/arm64/v8 builder 2/4] COPY . .                                                                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64 builder 3/4] RUN CGO_ENABLED=0 go build .                                                                                                                                                                                                                                                                                                             6.2s
 => [linux/arm64/v8 stage-1 2/2] COPY --from=builder /app/pingServer .                                                                                                                                                                                                                                                                                                 0.0s
 => exporting to oci image format                                                                                                                                                                                                                                                                                                                                      0.2s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                0.1s
 => => exporting manifest sha256:be374cd295134e5fef9ac3664ba6a7e4682850883e7c54a3dd8e912f8e263935                                                                                                                                                                                                                                                                      0.0s
 => => exporting config sha256:44926a73bb40b245abd7854aa4307e057153d567561bf6aeae344d3622311230                                                                                                                                                                                                                                                                        0.0s
 => => exporting manifest list sha256:1c4a4bb60ec176b69350dfc75fd65d12851e5da8ce51f671870d79f3d59efd52                                                                                                                                                                                                                                                                 0.0s
 => => sending tarball                                                                                                                                                                                                                                                                                                                                                 0.0s
demo:latest

That went well. Let’s run it. Keep in mind that we need to have port 8080 exposed so that we can receive requests:

$ container run -p 8080:8080 -ti --rm demo

Like in the normal go app, no output as expected, so let’s try to curl it:

$ curl localhost:8080
Hello, World

Great! If we ignore our first issue with the ps subcommand things do feel very smooth and also pretty much the same as when we were to use the regular docker commands.

Now that we know that things work, the next question is how well they work. If we time the above from clean, we get the following:

$ time container build -t demo .
[+] Building 21.0s (11/11) FINISHED
 => [resolver] fetching image...docker.io/library/golang                                                                                                                                                                                                                                                                                                               0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 210B                                                                                                                                                                                                                                                                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                                                                        0.0s
 => oci-layout://docker.io/library/golang@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                                      5.4s
 => => resolve docker.io/library/golang@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                                        0.0s
 => => sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747 126B / 126B                                                                                                                                                                                                                                                                             0.0s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                                                                                                                                                               0.0s
 => => sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15 64.18MB / 64.18MB                                                                                                                                                                                                                                                                       1.5s
 => => sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938 98.42MB / 98.42MB                                                                                                                                                                                                                                                                       1.7s
 => => sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1 67.60MB / 67.60MB                                                                                                                                                                                                                                                                       1.5s
 => => sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7 25.03MB / 25.03MB                                                                                                                                                                                                                                                                       0.6s
 => => sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563 49.67MB / 49.67MB                                                                                                                                                                                                                                                                       1.0s
 => => extracting sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563                                                                                                                                                                                                                                                                              0.6s
 => => extracting sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7                                                                                                                                                                                                                                                                              0.3s
 => => extracting sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1                                                                                                                                                                                                                                                                              0.8s
 => => extracting sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938                                                                                                                                                                                                                                                                              0.9s
 => => extracting sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15                                                                                                                                                                                                                                                                              1.1s
 => => extracting sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747                                                                                                                                                                                                                                                                              0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64/v8 stage-1 1/2] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.0s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                      0.1s
 => => transferring context: 7.94MB                                                                                                                                                                                                                                                                                                                                    0.1s
 => [linux/arm64/v8 builder 1/4] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.4s
 => [linux/arm64/v8 builder 2/4] COPY . .                                                                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64 builder 3/4] RUN CGO_ENABLED=0 go build .                                                                                                                                                                                                                                                                                                             6.2s
 => [linux/arm64/v8 stage-1 2/2] COPY --from=builder /app/pingServer .                                                                                                                                                                                                                                                                                                 0.0s
 => exporting to oci image format                                                                                                                                                                                                                                                                                                                                      0.2s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                0.1s
 => => exporting manifest sha256:be374cd295134e5fef9ac3664ba6a7e4682850883e7c54a3dd8e912f8e263935                                                                                                                                                                                                                                                                      0.0s
 => => exporting config sha256:44926a73bb40b245abd7854aa4307e057153d567561bf6aeae344d3622311230                                                                                                                                                                                                                                                                        0.0s
 => => exporting manifest list sha256:1c4a4bb60ec176b69350dfc75fd65d12851e5da8ce51f671870d79f3d59efd52                                                                                                                                                                                                                                                                 0.0s
 => => sending tarball                                                                                                                                                                                                                                                                                                                                                 0.0s
demo:latest

real    0m37.588s
user    0m4.192s
sys     0m3.058s

For comparison, the same build command with docker (after system prune -a) on the same machine:

$ time docker build -t docker .
[+] Building 13.5s (11/11) FINISHED                                                                                                                                                                                                                                                                                                                    docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 213B                                                                                                                                                                                                                                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/golang:latest                                                                                                                                                                                                                                                                                                       1.3s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                                                                        0.0s
 => [builder 1/4] FROM docker.io/library/golang:latest@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                         8.8s
 => => resolve docker.io/library/golang:latest@sha256:983a0823d3dab83604654972fe6bbda13142a7c57f987804fbdddb9d47dad9ec                                                                                                                                                                                                                                                 0.0s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                                                                                                                                                               0.1s
 => => sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747 126B / 126B                                                                                                                                                                                                                                                                             0.2s
 => => sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15 64.18MB / 64.18MB                                                                                                                                                                                                                                                                       2.6s
 => => sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1 67.60MB / 67.60MB                                                                                                                                                                                                                                                                       6.1s
 => => sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938 98.42MB / 98.42MB                                                                                                                                                                                                                                                                       6.4s
 => => sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7 25.03MB / 25.03MB                                                                                                                                                                                                                                                                       2.1s
 => => sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563 49.67MB / 49.67MB                                                                                                                                                                                                                                                                       2.1s
 => => extracting sha256:6b89e501e8efce0d3d87e3f6b0f85c417e799a3b36b8f44419609ba7fecf9563                                                                                                                                                                                                                                                                              0.5s
 => => extracting sha256:3198b4d4653b3225aa62931a3b1ff61435a6c99e83b6f2581bd52915332f78b7                                                                                                                                                                                                                                                                              0.2s
 => => extracting sha256:8f4f1d683f65087c4403277cc2e5a3d787025eb59d342271a327b23718904ab1                                                                                                                                                                                                                                                                              0.6s
 => => extracting sha256:dc98e650d0c56d3599a822f7359b264ff24cc3913019a68dc82e051c3dcbe938                                                                                                                                                                                                                                                                              0.8s
 => => extracting sha256:9e4d5c89bdd324edeb5721c09064e2659473bba39ae9d84fae53c9cc0181cf15                                                                                                                                                                                                                                                                              1.2s
 => => extracting sha256:670d42cf8b4364eac939465668b17e1a15c633e59b9bb4e088b31c3ca6c23747                                                                                                                                                                                                                                                                              0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                                                                                                                                                              0.0s
 => [stage-1 1/2] WORKDIR /app                                                                                                                                                                                                                                                                                                                                         0.0s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                      0.1s
 => => transferring context: 7.94MB                                                                                                                                                                                                                                                                                                                                    0.1s
 => [builder 2/4] WORKDIR /app                                                                                                                                                                                                                                                                                                                                         0.1s
 => [builder 3/4] COPY . .                                                                                                                                                                                                                                                                                                                                             0.0s
 => [builder 4/4] RUN CGO_ENABLED=0 go build .                                                                                                                                                                                                                                                                                                                         2.9s
 => [stage-1 2/2] COPY --from=builder /app/pingServer .                                                                                                                                                                                                                                                                                                                0.0s
 => exporting to image                                                                                                                                                                                                                                                                                                                                                 0.2s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                0.2s
 => => exporting manifest sha256:eb62207c6dd2610838e3816237c25a6ca0cba530c24fc1c38889464ce568277b                                                                                                                                                                                                                                                                      0.0s
 => => exporting config sha256:291ba548eecc8b8aae58e5f8873d347ba842dd4d5411288cd37dacbc5c2594ac                                                                                                                                                                                                                                                                        0.0s
 => => exporting attestation manifest sha256:9b484a1c2329cdf2153cb01dcfc77b0889d5c13a06d208aac496ad6b67495cb0                                                                                                                                                                                                                                                          0.0s
 => => exporting manifest list sha256:d6f61272e83ec867e5b895e0d175f5a6e676c77d8d18e2c85412bc80028771a9                                                                                                                                                                                                                                                                 0.0s
 => => naming to docker.io/library/docker:latest                                                                                                                                                                                                                                                                                                                       0.0s
 => => unpacking to docker.io/library/docker:latest                                                                                                                                                                                                                                                                                                                    0.0s

real    0m13.630s
user    0m0.167s
sys     0m0.169s

That is 1/3 the time. The performance comparison seems pretty clear.

Under the Hood

When looking at the results in the last section, I had some concerns. It seemed very weird that a native container engine should have severely worse performance than something that needs an additional Linux VM. To understand that, we need to look into the action implementation of Apple Containers.

Like we saw, when we initialized our system the first time for Container usage, we had to download some kernel. What’s that for? Turns out that we do actually run a “light-weight” VM for every container as stated in the docs. Now the question for us is if the situation is the same actually at build time, because that’s when we already saw the performance impact. The simple answer is “yes”. The more differentiated answer, however, is that the approach for build time and run time is slightly different. Apple Containers does not use a VM but multiple. Essentially, every container you run runs in its own lightweight VM, as opposed to the Docker approach, where basically all Containers run in the same VM. Now, the exception to the rule is, that for building images, there is just a single builder container. That obviously runs in its own VM again. The whole thing comes up fresh on the first time, which would explain why we are seeing those build times being so slow. It’s not explicitly stated in the docs, but assuming a fresh VM with a fresh container in it has to be provisioned first and then also doesn’t have a build cache, that does make sense. Let’s take it for another spin. We clean up the existing builder first:

$ container builder delete --foorce
Error: Unknown option '--foorce'. Did you mean '--force'?
Usage: container builder delete [--force] [--debug]
  See 'container builder delete --help' for more information.

With --force it just does it, so no output to show here.

Next, we start the builder explicitly before we run our build command, assuming that we start a fresh container and a fresh VM that also has no build cache or anything, so a situation similar situation to Docker after we ran the system prune command:

$ container builder start

Yet again there is no output, but I guess that just means that all is fine.

Now let’s run the build:

$ time container build -t demo .
[+] Building 20.9s (11/11) FINISHED
 => [resolver] fetching image...docker.io/library/golang                                                                                                                                                                                                                                                                                                               0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 210B                                                                                                                                                                                                                                                                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                                                                        0.0s
 => oci-layout://docker.io/library/golang@sha256:0ecdc2a9f6156af6451080bfe3d8382a662fcc4e209608c6f919e643453514c1                                                                                                                                                                                                                                                      5.4s
 => => resolve docker.io/library/golang@sha256:0ecdc2a9f6156af6451080bfe3d8382a662fcc4e209608c6f919e643453514c1                                                                                                                                                                                                                                                        0.0s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                                                                                                                                                               0.0s
 => => sha256:3248ea96287c1c219864b234f06742e9bb4a50f6e1a1dc3b7d79b3eb379dd968 126B / 126B                                                                                                                                                                                                                                                                             0.0s
 => => sha256:7c7d71ede3d0f0c6bd07ffa6581b7c523b2a236cd9c9a0151e44c9cfe43011ab 67.63MB / 67.63MB                                                                                                                                                                                                                                                                       1.6s
 => => sha256:02ae4aa9377d26806ae3cd8b394304961f5e283aaf748a9bada644611d444ed6 98.47MB / 98.47MB                                                                                                                                                                                                                                                                       1.8s
 => => sha256:42fd700369b465b3f57ff668eea94fc0b0e67029df25a71600d2c522a452406d 67.60MB / 67.60MB                                                                                                                                                                                                                                                                       1.6s
 => => sha256:7fa3c72c6402ef534e043b1dca6fc0ab99e044c333c7679d2508eac3817a8570 25.03MB / 25.03MB                                                                                                                                                                                                                                                                       0.6s
 => => sha256:7f50a08a25277c02b8dfc99818a21e12083215981e0aa03cb07cb74c9116f205 49.70MB / 49.70MB                                                                                                                                                                                                                                                                       1.0s
 => => extracting sha256:7f50a08a25277c02b8dfc99818a21e12083215981e0aa03cb07cb74c9116f205                                                                                                                                                                                                                                                                              0.6s
 => => extracting sha256:7fa3c72c6402ef534e043b1dca6fc0ab99e044c333c7679d2508eac3817a8570                                                                                                                                                                                                                                                                              0.3s
 => => extracting sha256:42fd700369b465b3f57ff668eea94fc0b0e67029df25a71600d2c522a452406d                                                                                                                                                                                                                                                                              0.7s
 => => extracting sha256:02ae4aa9377d26806ae3cd8b394304961f5e283aaf748a9bada644611d444ed6                                                                                                                                                                                                                                                                              0.9s
 => => extracting sha256:7c7d71ede3d0f0c6bd07ffa6581b7c523b2a236cd9c9a0151e44c9cfe43011ab                                                                                                                                                                                                                                                                              1.2s
 => => extracting sha256:3248ea96287c1c219864b234f06742e9bb4a50f6e1a1dc3b7d79b3eb379dd968                                                                                                                                                                                                                                                                              0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64/v8 stage-1 1/2] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.0s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                      0.1s
 => => transferring context: 7.94MB                                                                                                                                                                                                                                                                                                                                    0.1s
 => [linux/arm64/v8 builder 1/4] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.3s
 => [linux/arm64/v8 builder 2/4] COPY . .                                                                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64 builder 3/4] RUN CGO_ENABLED=0 go build .                                                                                                                                                                                                                                                                                                             6.0s
 => [linux/arm64/v8 stage-1 2/2] COPY --from=builder /app/pingServer .                                                                                                                                                                                                                                                                                                 0.0s
 => exporting to oci image format                                                                                                                                                                                                                                                                                                                                      0.2s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                0.1s
 => => exporting manifest sha256:00f39c80abf195cdf5883fe5c4c195e6a61e39a68b3bf698d81e8dedb49a9459                                                                                                                                                                                                                                                                      0.0s
 => => exporting config sha256:dbd1697c89acd85c15e75963725b110b287a42a61f59385fb9cdcb6fe6e03387                                                                                                                                                                                                                                                                        0.0s
 => => exporting manifest list sha256:f9f5485ac0b0078e9fa9cfa3b830685de446f60d087bb2cf1a2ff86484bf9de0                                                                                                                                                                                                                                                                 0.0s
 => => sending tarball                                                                                                                                                                                                                                                                                                                                                 0.0s
demo:latest

real    0m21.361s
user    0m2.713s
sys     0m2.236s

That does already look significantly better than before. The reaming difference can actually be well caused by the fact that I allocated 8 CPUS and 8 Gig of memory to Docker, whereas the Apple Containers builder just comes up with 2 CPUs and 2GB memory by default. We can change that before startup like so:

$ container builder start --cpus 8 --memory 8g

Let’s test again:

$ time container build -t demo .
[+] Building 18.0s (11/11) FINISHED
 => [resolver] fetching image...docker.io/library/golang                                                                                                                                                                                                                                                                                                               0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 210B                                                                                                                                                                                                                                                                                                                                   0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                      0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                                                                        0.0s
 => oci-layout://docker.io/library/golang@sha256:0ecdc2a9f6156af6451080bfe3d8382a662fcc4e209608c6f919e643453514c1                                                                                                                                                                                                                                                      5.3s
 => => resolve docker.io/library/golang@sha256:0ecdc2a9f6156af6451080bfe3d8382a662fcc4e209608c6f919e643453514c1                                                                                                                                                                                                                                                        0.0s
 => => sha256:3248ea96287c1c219864b234f06742e9bb4a50f6e1a1dc3b7d79b3eb379dd968 126B / 126B                                                                                                                                                                                                                                                                             0.0s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                                                                                                                                                                                                               0.0s
 => => sha256:7c7d71ede3d0f0c6bd07ffa6581b7c523b2a236cd9c9a0151e44c9cfe43011ab 67.63MB / 67.63MB                                                                                                                                                                                                                                                                       1.7s
 => => sha256:02ae4aa9377d26806ae3cd8b394304961f5e283aaf748a9bada644611d444ed6 98.47MB / 98.47MB                                                                                                                                                                                                                                                                       1.9s
 => => sha256:42fd700369b465b3f57ff668eea94fc0b0e67029df25a71600d2c522a452406d 67.60MB / 67.60MB                                                                                                                                                                                                                                                                       1.7s
 => => sha256:7fa3c72c6402ef534e043b1dca6fc0ab99e044c333c7679d2508eac3817a8570 25.03MB / 25.03MB                                                                                                                                                                                                                                                                       0.6s
 => => sha256:7f50a08a25277c02b8dfc99818a21e12083215981e0aa03cb07cb74c9116f205 49.70MB / 49.70MB                                                                                                                                                                                                                                                                       1.1s
 => => extracting sha256:7f50a08a25277c02b8dfc99818a21e12083215981e0aa03cb07cb74c9116f205                                                                                                                                                                                                                                                                              0.6s
 => => extracting sha256:7fa3c72c6402ef534e043b1dca6fc0ab99e044c333c7679d2508eac3817a8570                                                                                                                                                                                                                                                                              0.2s
 => => extracting sha256:42fd700369b465b3f57ff668eea94fc0b0e67029df25a71600d2c522a452406d                                                                                                                                                                                                                                                                              0.7s
 => => extracting sha256:02ae4aa9377d26806ae3cd8b394304961f5e283aaf748a9bada644611d444ed6                                                                                                                                                                                                                                                                              0.9s
 => => extracting sha256:7c7d71ede3d0f0c6bd07ffa6581b7c523b2a236cd9c9a0151e44c9cfe43011ab                                                                                                                                                                                                                                                                              1.2s
 => => extracting sha256:3248ea96287c1c219864b234f06742e9bb4a50f6e1a1dc3b7d79b3eb379dd968                                                                                                                                                                                                                                                                              0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64/v8 stage-1 1/2] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.0s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                      0.1s
 => => transferring context: 7.94MB                                                                                                                                                                                                                                                                                                                                    0.1s
 => [linux/arm64/v8 builder 1/4] WORKDIR /app                                                                                                                                                                                                                                                                                                                          0.7s
 => [linux/arm64/v8 builder 2/4] COPY . .                                                                                                                                                                                                                                                                                                                              0.0s
 => [linux/arm64 builder 3/4] RUN CGO_ENABLED=0 go build .                                                                                                                                                                                                                                                                                                             3.0s
 => [linux/arm64/v8 stage-1 2/2] COPY --from=builder /app/pingServer .                                                                                                                                                                                                                                                                                                 0.0s
 => exporting to oci image format                                                                                                                                                                                                                                                                                                                                      0.2s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                0.2s
 => => exporting manifest sha256:640cb4b8482aae3e3e0bd4ec9f62a188ca4e60ba25a5da8e8014b91706895fd8                                                                                                                                                                                                                                                                      0.0s
 => => exporting config sha256:6222ed8eae20692747b51d7887569d09a50d3fcaf8e6f2e8d2a1bbce50f061de                                                                                                                                                                                                                                                                        0.0s
 => => exporting manifest list sha256:37e81f169edcfde3646b7f0a1853247b94b469a730ea30b74a473fdd889aa9ce                                                                                                                                                                                                                                                                 0.0s
 => => sending tarball                                                                                                                                                                                                                                                                                                                                                 0.0s
demo:latest

real    0m18.793s
user    0m2.603s
sys     0m2.198s

Good enough for me to call it even. The biggest chunk of time went into downloading the base images, and we are not doing quality measurements for that, since this is running on a laptop on my home internet connection via Wi-Fi. So there’s no guarantee the conditions where exactly the same.

Conclusion

Can you use Apple Containers? Yes.

Can you use them as a replacement for Docker? To an extent. You can run containers, and you can build them, which probably covers a lot of “normal” use cases. Additionally, the community seems to adopt it well, given also the comments under the minikube issue requestin support for Apple containers. If people are curious about that, let me know, and maybe there will be another blog looking deeper into that.


See also