Replacing Docker Desktop with Apple's Container Stack

Docker Desktop has been the default for running Linux containers on macOS for years. It works, but it's heavy — an Electron app, a full Linux VM, background processes eating RAM, and licensing restrictions for larger organizations. Apple now ships its own container runtime for Apple Silicon, and it's fast, lightweight, and free. Here's how I replaced Docker Desktop entirely.

The stack

Five tools, each handling a different layer:

container — Apple's open-source container runtime. Runs Linux containers natively on Apple Silicon using the Virtualization framework. No heavyweight VM, no daemon in the traditional Docker sense.

Davit — A free, open-source, fully native macOS app that talks directly to Apple's container daemon over XPC. Containers, images, volumes, networks, live stats, logs, file browsing, Compose import, Dockerfile builds. No Electron, no web views.

socktainer — Exposes a Docker-compatible socket and translates Docker Engine API calls to Apple's container runtime. Anything expecting /var/run/docker.sock keeps working — Docker Compose, Testcontainers, docker-py, CI tools.

container-compose — Docker Compose equivalent for the container runtime. Reads your existing docker-compose.yml files and orchestrates multi-container stacks through Apple's runtime directly.

docker-container-shim — A shell function that rewrites docker CLI commands to container CLI syntax so muscle memory still works.

Installing container

brew install container

Davit can also handle this — if it doesn't detect the runtime on first launch, it downloads Apple's signed installer and sets everything up in your user Library without admin rights.

Installing Davit

Download from davit.app or install via Homebrew.

Davit gives you a GUI for everything the CLI does and more — interactive shell access straight into Terminal or iTerm, in-container file browsing with upload/download/delete, registry auth stored in your login keychain (shared with the container CLI), and live CPU/memory stats across all running containers.

The Compose import is particularly nice. Open a docker-compose.yml and Davit shows you exactly what it will create — services in dependency order, volumes, networks, the equivalent CLI command per service, and honest warnings for anything unsupported. Then it starts the whole stack in one click.

Installing socktainer

brew install socktainer

This is the compatibility layer for anything that doesn't use the docker CLI directly. SDKs, language libraries, CI tooling — they all speak the Docker Engine API over a Unix socket. Socktainer bridges that gap so those tools keep working without Docker Desktop installed.

Installing container-compose

brew install container-compose

If you have existing docker-compose.yml files, container-compose handles them natively through Apple's runtime. No socket translation needed — it talks to the container daemon directly.

The CLI shim

The last piece is muscle memory. I've typed docker ps and docker build thousands of times. This shell function wraps docker and rewrites it to container syntax:

# docker → container shim
docker() {
  if ! command -v container >/dev/null 2>&1; then
    echo "docker (shim): 'container' CLI not found." >&2
    return 127
  fi

  local cmd="$1"
  [ $# -gt 0 ] && shift

  case "$cmd" in
    ps)
      local args=()
      for a in "$@"; do
        case "$a" in
          -a|--all) args+=(--all) ;;
          *) args+=("$a") ;;
        esac
      done
      container list "${args[@]}"
      ;;
    images)       container image list "$@" ;;
    pull)         container image pull "$@" ;;
    push)         container image push "$@" ;;
    tag)          container image tag "$@" ;;
    rmi)          container image delete "$@" ;;
    save)         container image save "$@" ;;
    load)         container image load "$@" ;;
    login)        container registry login "$@" ;;
    logout)       container registry logout "$@" ;;
    info)         container system status "$@" ;;
    version)      container system version "$@" ;;
    run|build|create|start|stop|kill|rm|exec|logs|inspect|stats|cp|prune|network|volume|system)
      container "$cmd" "$@"
      ;;
    compose)
      if command -v container-compose >/dev/null 2>&1; then
        container-compose "$@"
      else
        echo "docker (shim): 'container-compose' not found. Install with: brew install container-compose" >&2
        return 127
      fi
      ;;
    *)
      echo "docker (shim): no translation for '$cmd', passing through." >&2
      container "$cmd" "$@"
      ;;
  esac
}

Source it in your ~/.zshrc:

source /path/to/docker-container-shim.sh

Now docker ps, docker build -t myapp ., docker compose up -d — all of it works, routed through Apple's runtime.

How the layers fit together

Image

Four paths into one runtime. The shim handles direct CLI usage. Container-compose handles multi-container stacks natively. Socktainer catches everything that talks over the socket — SDKs, CI tools, anything expecting a Docker daemon. And Davit gives you a native GUI over all of it.

Caveats

Apple Silicon only. The container runtime uses the Virtualization framework, which is arm64.

The shim is best-effort. It covers the commands I use daily. If something behaves unexpectedly, container --help and container <cmd> --help are the source of truth.

Some Docker CLI flags don't have direct equivalents in container and get dropped. The shim handles the common ones (-a/--all on ps, for example) but edge cases may need manual translation.

The result

No license concerns — everything here is open-source and free. Lower resource usage — no Electron app, no background daemon eating memory. Native performance on Apple Silicon. And between socktainer, container-compose, and the shim, existing workflows keep working without changes.