Documentation menu

klimax as a Docker host

klimax runs a Docker daemon inside its VM to host kind clusters. That daemon is an ordinary one — you can point your Mac at it and use it for anything, with no Docker Desktop installed.

Point Docker at it

Two ways, and they conflict — pick one. A Docker context persists across shells and is the better default:

$ klimax docker-context        # create and switch to the "klimax" context
$ docker ps                    # now talks to the VM
$ klimax docker-context --unset # switch back to "default"

Or set DOCKER_HOST for the current shell only:

$ eval $(klimax docker-env)
$ eval $(klimax docker-env --unset)

DOCKER_HOST overrides the active context. If you set both you will eventually confuse yourself about which daemon you are talking to — use one mechanism.

Either way the socket is a Unix socket at ~/.klimax.docker.sock, forwarded by Lima. Nothing listens on a TCP port, so nothing on your network can reach the daemon.

Docker Compose

docker compose is a client-side plugin, not something the daemon provides. The klimax VM ships it, so klimax shell docker compose version works — but that says nothing about your Mac, where the docker CLI looks for plugins in ~/.docker/cli-plugins/. Installing Compose with Homebrew does not put it there:

$ brew install docker-compose
$ docker compose version
docker: unknown command: docker compose   # the binary exists, the plugin link does not

Link it once:

$ mkdir -p ~/.docker/cli-plugins
$ ln -sfn /opt/homebrew/opt/docker-compose/bin/docker-compose \
        ~/.docker/cli-plugins/docker-compose
$ docker compose version

Homebrew suggests an alternative that picks up future plugins automatically — add to ~/.docker/config.json:

{
  "cliPluginsExtraDirs": ["/opt/homebrew/lib/docker/cli-plugins"]
}
🔌

This is not specific to klimax — the same applies to any Docker daemon you point a Homebrew-installed CLI at. It is worth knowing here because Docker Desktop bundles its own CLI with the plugins already wired, so people moving away from it meet this on their first docker compose up.

Published ports on localhost

This is the part worth configuring deliberately. Set:

network:
  disablePortMirroring: false

and a container's published ports appear on your Mac's loopback, exactly as they would with Docker Desktop:

$ docker run -d -p 8080:80 nginx:alpine
$ curl http://localhost:8080/   # 200 — no port-forward, no VM IP

Lima's hostagent watches for listening ports inside the VM and mirrors each one to 127.0.0.1 on the host as it appears. You publish a port the way you always have, and it lands where you expect.

🔌

Why this is not the default. klimax defaults to disablePortMirroring: true so it can coexist with other Lima-based tools that manage kind clusters — Rancher Desktop, kind-on-lima, Colima. Two VMs both mirroring an API server on port 7001 to 127.0.0.1 break each other. If klimax is your only Lima VM, or you are using it primarily as a Docker host, turning mirroring on is the friendlier choice.

The trade-off is small and worth knowing: with mirroring on, cluster kubeconfigs use 127.0.0.1, which is stable across VM restarts. With it off, they use the VM's lima0 IP, which macOS assigns and can change — so kubeconfigs need refreshing after a restart. Turning mirroring on actually removes that chore.

This is a VM-level setting: it applies when the instance is created. Changing it needs klimax destroy && klimax up, which deletes your clusters. Decide before you build a lab on it.

Bind mounts

The daemon runs inside the VM and resolves -v sources there. A path your Mac has but the VM does not will not fail — the guest creates an empty directory and the container sees nothing in it. Share the directory first:

vm:
  mounts:
    - location: "~/projects"
      writable: true

Each entry appears at the same absolute path inside the VM, which is what makes docker run -v ~/projects/app:/app resolve. See Sharing host directories.

Registry mirrors and the docker CLI

klimax runs pull-through caches for Docker Hub, quay.io, gcr.io and Artifact Registry, and all of them serve the docker CLI, not just cluster pulls. A docker pull or docker compose up is answered from the local cache on the second fetch, and Docker Hub rate limits stop biting.

Cluster pulls and CLI pulls reach the caches through separate config trees, because two different daemons are involved:

ConsumerReadsWritten by
docker pull / docker compose/etc/docker/certs.d/<host>/hosts.tomlklimax up, in the VM
kind cluster nodes/etc/containerd/certs.d/<host>/hosts.tomlklimax cluster create, per node

Both use containerd's hosts.toml format — dockerd calls containerd's loader for it — but neither daemon reads the other's directory. Despite the name, certs.d is a general per-registry configuration directory: it held only TLS material when Docker introduced it, and hosts.toml was added to the same layout later to declare mirrors.

Check what a pull will do:

$ klimax shell sudo ls /etc/docker/certs.d
docker.io
gcr.io
quay.io
us-central1-docker.pkg.dev
us-docker.pkg.dev
🧩

This needs Docker's containerd image store, the default since Docker 28 and what the klimax VM uses. On the older graphdriver store, dockerd resolves registries with its own client and can only mirror Docker Hub — so klimax also writes a Hub mirror into daemon.json, which covers that case.

A mirror is only ever an accelerator: hosts.toml records the real registry as a fallback, so if a cache is down the pull goes direct rather than failing.

⚠️

If you write these files yourself, the endpoint must be 127.0.0.1:<port>, not the mirror's container name. dockerd runs in the VM's own network namespace, where registry-dockerio does not resolve — and a mirror dockerd cannot reach is skipped silently, so pulls keep working while nothing is ever cached.

What you get over a bare Docker daemon

FeatureWhat it means here
Pull-through registry mirrorsDocker Hub, quay.io, gcr.io and Artifact Registry are cached locally, for the docker CLI as well as for clusters. Re-pulls are local and Docker Hub rate limits stop biting — see above.
Persistent image storevm.imageDisk keeps images on their own disk, so klimax destroy does not throw away your image cache.
Rosetta for amd64vm.rosetta: true runs amd64 images on Apple Silicon.
Proxy and CA supportnetwork.proxy and vm.caCerts make it work on a corporate network — see Behind a corporate proxy.
Kubernetes when you want itThe same daemon hosts kind clusters. A Docker host today, a multi-cluster lab tomorrow, no second VM.

Alongside Docker Desktop, OrbStack or Colima

They can all be installed at once — each provides its own Docker context, and you switch between them:

$ docker context ls
NAME              DESCRIPTION                       DOCKER ENDPOINT
default           Current DOCKER_HOST based ...     unix:///var/run/docker.sock
klimax *                                            unix:///Users/you/.klimax.docker.sock

What they cannot all do at once is publish to the same port on 127.0.0.1. If you run klimax with disablePortMirroring: false next to another tool, give your containers distinct host ports.

🧭

If you want a Docker daemon and nothing else, OrbStack or Colima start faster and are built for that. klimax earns its place when you also want kind clusters with routable LoadBalancer IPs — see klimax vs. other tools.

See also