Yasser Sheikh · MSc BEng AMIChemE RITTech

Writing · Sep 2026

The best container runtime for a Mac, benchmarked.

A Linux container on a Mac runs inside a Linux virtual machine, and the runtime is the layer between that machine and yours. Most teams inherit one and never revisit it. This is a measured comparison of the five current options, Docker Desktop,OrbStack, Apple's container,Podman and Colima, on an M2 Pro, against a workload shaped like a Python data platform: a scheduler, a time-series database, a relational database, a broker, and images built from a lockfile with sixty scientific packages.

None is fastest in general. Each is fastest at one boundary, and the ranking flips depending on whether the work crosses the host filesystem, a database's write path, the network, or an emulated CPU. Each runtime keeps the same colour and position in every chart and table.

The trade that decides itup and left is better
x: seconds to unpack 200,000 files onto a host bind mount (log). y: pgbench transactions per second. Nobody wins both.
If the day is dominated byPickBecause
A source tree mounted into containersOrbStack3 to 4x faster at that boundary, fastest to start, best under memory pressure, joint top on compatibility, $8 per user per month against $24 for Docker Business
A local databaseDocker Desktoptwice OrbStack's write throughput on volumes, fastest rebuilds and container starts
x86 images, rootless, or no budgetColima or Podmanfree; set --vz-rosetta at creation on Colima; neither mounts its socket into a container
A few containers in strong isolationApple containerbest emulated x86, smallest idle footprint; not ready to standardise on: no Compose, no Engine API, per-container memory and network costs

Test bench

MachineMacBook Pro, Apple M2 Pro, 10 cores (6P + 4E), 32 GB, macOS 26.6.2
Per runtime6 vCPU, 8 GB; every container also run with --cpus 6 --memory 8g
Client toolsdocker CLI 29.8.0, compose 5.5.1, buildx 0.37.0, shared by every Docker-API runtime
Base imagespython 3.14.7-slim-trixie, postgres 18, influxdb 2.9.1, redis 8, rabbitmq 4, apache/airflow 3.3.1, uv 0.12.10
Not includedRancher Desktop (installer conflicts with Docker Desktop; same Lima family as Colima) and Finch (needs interactive sudo; same family)
RuntimeVersionArchitecture
OrbStack2.2.3, engine 29.4.0one lightweight VM, its own filesystem bridge
Docker Desktop4.90.0, engine 29.7.2one VM, moby, VirtioFS
Apple container1.3.1one micro-VM per container, vmnet, an IP each
Colima0.10.3, Lima 2.2.0one VM (vz), dockerd, virtiofs
Podman6.1.1one VM (applehv), crun, rootless

Method

IsolationExactly one runtime alive at a time, verified against the hypervisor processes. Every other runtime stopped before each block.
RepeatsCore suite: 3 rounds, runtime order reshuffled each round from a fixed seed; 12 repeats inside the container-start metric. Application, stress, fan-out and compatibility passes: once each; where they produce a rate, the rate is compared.
StatisticsMedians. Three-round metrics show min and max beneath the median in tables and as whiskers in charts. Failures are reported with their error, never imputed; a throughput of exactly zero is treated as a failure to investigate.
Drift controlA fixed single-core host task ran before and after every block. Baselines across all blocks: 0.612 to 0.644 s. Drift within a block: -6.2% to +1.5%. No block was discounted for drift.
Cold stateImage stores purged before each pull measurement and at the end of each block. 20 s thermal settle before each block.
Load evidenceA host sampler recorded CPU, memory, swap and disk every 2 s during the heavy passes: CPU peaked at 68 to 100%, memory at 16 to 20 GB of 32, swap at 2.7 GB on every runtime.
Pre-registrationTen hypotheses written before any data. Scored at the end, including the one that failed.
DataAbout 750 raw measurements across nine passes; 17 logged deviations, the material ones listed at the end.

Start and pull

Apple's runtime is ready fastest and starts each container slowest, because each one is a VM. Its image pulls are six times slower than the others for the same four images.

OrbStackDocker DesktopApple containerColimaPodman
Cold start to first container s1.251.14-2.654.162.77-12.31.661.35-1.8813.913.4-14.510.810.7-10.9
Warm container start, p50 of 12 s0.280.27-0.280.130.13-0.141.020.91-1.120.170.17-0.180.190.18-0.19
Pull four images s20.320.2-20.818.618.3-44.7132131-18021.821.1-23.529.025.1-40.6

Compute

Under seven per cent spread on every compute metric. All five sit on the same hypervisor and the same silicon; work that stays inside the container does not see the runtime.

OrbStackDocker DesktopApple containerColimaPodman
Single-core throughput MiB/s25282508-255424922467-249825562555-255724432372-247824442384-2523
Six-core throughput MiB/s12,97512,646-13,12512,80612,555-12,81513,02912,903-13,03212,19410,703-12,60512,48811,559-12,849
Airflow, 8M rows, arm64 rows/s469,491468,832485,682463,919474,790

The host filesystem boundary

The same untar, checksum and delete cycle on a host bind mount, where a mounted source tree lives, and on a native volume, which never leaves the VM. Bind-mount times differ four times across runtimes; volume times differ two, and the order inverts. The lines below are near-parallel, so the gap is a constant factor, not a scaling difference.

Seconds to unpack a source treesolid bind mount · dashed volume
OrbStackDocker DesktopApple containerColimaPodman
Untar 20k files, bind mount s2.252.22-2.366.906.83-6.928.378.37-8.399.168.87-9.749.619.48-10.1
Checksum 20k files, bind mount s0.580.56-0.582.011.98-2.032.782.76-2.803.543.40-3.804.264.04-4.43
Untar 20k files, volume s0.160.15-0.160.210.21-0.220.150.15-0.170.230.23-0.240.290.26-0.30
Untar 200k files, bind mount s23.567.886.992.593.4
Checksum 200k files, bind mount s6.8025.739.139.848.3
Untar 200k files, volume s8.992.261.772.693.53
Random 4 KiB writes, bind mount IOPS48,67723,54511,23910,25910,432
Random 4 KiB writes, volume IOPS17,85436,91443,72841,23839,515
Small-file ops under full contention, bind mount ops/s14,4701120143724021824

Build

Cold builds differ by a factor of two. Rebuilding after a one-line source change differs by nearly twelve, and that is the operation paid dozens of times a day.

Seconds to build the 60-package imagelog scale
OrbStackDocker DesktopApple containerColimaPodman
Cold build, 60 packages s51.548.9-53.335.735.3-11359.154.4-67.346.846.1-47.271.770.2-82.4
Rebuild after a source change s2.442.38-2.510.740.73-4.778.618.16-11.91.401.37-1.528.347.22-11.7
Four cold builds at once s60.239.777.160.0137

Network

One stream flatters the published-port path; sixteen at once show it under load. Docker Desktop moves 2,057 MiB/s on one stream and 152 across sixteen. Apple's container-to-container figure is low because that traffic crosses a virtual network between two VMs rather than staying inside one kernel.

Host to container, MiB/stinted 1 stream · solid 16 streams
OrbStackDocker DesktopApple containerColimaPodman
Host to container, 1 stream MiB/s47084403-476420572048-2058648648-649297282-306270235-270
Host to container, 16 streams MiB/s76461524602301273
Container to container MiB/s11,54011,466-11,91511,1899222-12,518159915,02714,183-15,44917,35515,892-17,502
New connections per second conn/s40842024286515261654
Round trip, p99 ms0.340.590.561.080.57

Under heavy load

pgbench at scale 150 (2.2 GB) with 32 clients for two minutes; five minutes of simultaneous CPU, small-file and block-I/O contention; three containers each holding a 4 GiB working set against an 8 GB ceiling; up to fifty containers at once. OrbStack is three to four times faster on the host filesystem and posts half Docker Desktop's database throughput, which matches its random-write figure on volumes. A source tree and a database sit on opposite sides of the same trade.

OrbStackDocker DesktopApple containerColimaPodman
pgbench, 32 clients tps787416,460520515,16413,582
Containers surviving 12 GiB against 8 GB of 332311
Time to start 20 containers s1.700.747.540.861.31
Four-service stack ready s2.542.665.103.053.07
Seconds to start N idle containerslog-log
A VM per container is a straight line with a steeper intercept: Apple takes 40 s to reach fifty, the shared-VM runtimes 4 to 10 s.

Airflow and InfluxDB, arm64 and amd64

Airflow 3.3.1 running a three-task pipeline that moves eight million rows through SQLite. InfluxDB 2.9.1 under four concurrent writers pushing ten million points, then four full-range Flux queries. Both repeated with the whole stack emulated onlinux/amd64, at 2M rows and 1M points, compared as rates. Natively the runtime barely matters; emulated, it decides everything.

InfluxDB v2, four writers · points per secondtinted native arm64 · solid emulated amd64
Airflow pipeline · rows per secondtinted native arm64 · solid emulated amd64
Native bars sit within a few per cent of each other. The emulated bars are the story, and the number after each is how many times slower than native.
OrbStackDocker DesktopApple containerColimaPodman
InfluxDB v2, 4 writers, arm64 points/s1,049,5911,195,788893,5161,130,7551,220,473
InfluxDB v2, 4 writers, emulated amd64 points/s135,184182,102200,33767,79244,345
InfluxDB, emulated vs native x slower7.766.574.4616.727.5
Airflow 8M-row pipeline, arm64 rows/s469,491468,832485,682463,919474,790
Airflow 2M-row pipeline, emulated amd64 rows/s164,390166,529169,36928,05920,721
Airflow, emulated vs native x slower2.862.822.8716.522.9

The Rosetta flag

On a synthetic CPU test Colima's emulation penalty was 63.82x against 5.4 to 10.3 for the others. Rosetta is off by default in Colima and is requested when the VM is created. Rebuilt with --vz-rosetta the penalty fell to5.22x, the lowest of any runtime. A creation-time flag is one most teams will not pass. Native beats emulated by enough everywhere that building arm64 images for local work is worth doing whatever production runs on.

OrbStackDocker DesktopApple containerColimaPodmanColima + Rosetta
CPU, emulated vs native x slower6.665.385.3763.810.35.22

Disk

Measured with each runtime stopped, so shutdown compaction is counted. Three of five return space when images are deleted. Apple stores images as expanded per-image filesystems, so its loaded footprint is large, and all of it comes back. Colima's purge left ten images behind, so its figures are an upper bound.

RuntimeEmptyFour imagesAfter deleting themReturns space
OrbStack0.06 GB1.38 GB0.06 GByes
Docker Desktop3.73 GB4.72 GB3.73 GByes
Apple container1.81 GB39.36 GB1.81 GByes
Colima13.4 GB14.73 GB14.73 GBno
Podman56.23 GB56.23 GB56.23 GBno

Compatibility

Ten features a typical project depends on. The gaps stop work rather than slow it: no Engine API means socket-driven tooling cannot see the runtime; no Compose means the stack file does not run; no socket mount means Testcontainers does not run. Podman's Compose failure is one service, RabbitMQ, which took the stack down with it. Started by hand with plain run commands, every runtime brought the same four services up in 2.5 to 5.1 s.

OrbStackDocker DesktopApple containerColimaPodman
Docker Engine APIyesyesnoyesyes
Host bind mountyesyesyesyesyes
Named volumeyesyesyesyesyes
BuildKit cache mountyesyesyesyesyes
Multi-stage --targetyesyesyesyesyes
Published portyesyesyesyesyes
Container-name DNSyesyesadmin onlyyesyes
Compose with healthchecksyesyesnoyesRabbitMQ fails
amd64 emulationyesyesyesyesyes
Socket mount (Testcontainers)yesyesnonono

Hypotheses

Written before the data existed; scored against it afterwards.

PredictionVerdictEvidence
H1Raw compute does not differ between runtimessupportedsingle-core spread 4.6%, six-core 6.8%, Airflow 4.7%
H2The host filesystem boundary dominates the dev loopsupportedbind mount 14 to 40 times slower than a volume
H3Bind-mount implementations differ more than volume implementationssupported4.3x spread on bind mounts, 1.9x on volumes
H4Apple trades per-container cost for isolationsupportedslowest start, best single core, 327 MB per container
H5Memory per container rises with a VM per containerpartialApple 327 MB each; the others sit below the method’s noise
H6Pull speed is implementation-bound, not network-boundsupported132 s against 19 to 29 s for identical images
H7x86 emulation is a large, runtime-dependent penaltysupported2.8x to 27.5x; one runtime moves 12x on a flag
H8Compatibility, not speed, decides adoptionsupportedApple 6 of 10 features; Podman loses Compose to one service
H9Sparse VM disk images never return spacerefutedthree of five returned it in full
H10The inner loop differs more than the cold buildsupported11.6x spread on rebuild, 2.0x on cold build

What went wrong

Each of these produced a clean, plausible, wrong number, and none announced itself. They were caught by re-checking the surprising results by hand. The full list of seventeen deviations is in the protocol file next to the raw data; these are the ones that changed a result.

FaultEffectResolution
Podman readiness checkqueried a field that does not exist in its output; three rounds timed outfixed, re-run
Apple address lookupread the wrong JSON level and fell back to a name it cannot resolve; container-to-container and InfluxDB recorded zerofixed, re-run
Stack test networkservices on the default bridge, which resolves no names; timed out identically on all fivefixed, re-run
Registry outageVPN resolver returned dead addresses for the registry; one round lostre-run as round 4
Concurrent passesfive queued passes started at once and ran against different runtimes togetherdiscarded, rebuilt sequentially
Disk methoddu on a sparse VM image cannot see image growthreplaced with a stopped-VM measurement
Build-secret checknever passed --secret, so every runtime failed a feature all of them supportrow removed from the matrix
Colima without Rosettaa 63.8x emulation penalty that is a creation-time flag, not a defectre-tested with the flag: 5.2x

The right question is not which runtime is fastest but which boundary the day's work crosses. For a source tree mounted into containers it is OrbStack by a wide margin; for a local database it is Docker Desktop by a similar one; for x86 images on a budget it is Colima with one flag set. Apple's runtime is the most interesting of the five and the least ready.