One binary instead of Docker Desktop, and it is also Kubernetes

A typical laptop setup for backend work has two engines. One runs containers: Docker Desktop, OrbStack, Colima, or dockerd on Linux. The other runs Kubernetes: kind or k3d inside the first one, or the Kubernetes that Docker Desktop can switch on. Two things to install, two to keep updated, two places your images live.
litek8s dev up is one command that gives you both. It starts a local
Kubernetes cluster and, on a socket next to it, the Docker Engine API. The
real docker CLI, docker compose, Dev Containers and Testcontainers talk
to that socket unchanged; kubectl and Helm talk to the same cluster.
Two minutes
litek8s dev upexport DOCKER_HOST=unix://$HOME/.litek8s/docker.sock
docker run --rm -it alpine shdocker compose up -d --build
kubectl create deployment web --image=nginxkubectl expose deployment web --type=NodePort --port 80curl localhost:$(kubectl get svc web -o jsonpath='{.spec.ports[0].nodePort}')The part we use most: the Docker socket and the cluster share one image
store, so an image you build is a pod’s image: right away. No registry,
no kind load:
DOCKER_BUILDKIT=0 docker build -t myapp:dev . # litek8s's builderkubectl create deployment myapp --image=myapp:devIt runs next to an installed Docker or OrbStack, on its own socket.
Nothing of theirs is stopped, changed or used. Leave DOCKER_HOST unset
and you talk to them as before, or switch with a context:
docker context create litek8s --docker host=unix://$HOME/.litek8s/docker.sockdocker context use litek8s # docker context use default (or orbstack) to go backSo trying it costs nothing: you do not have to uninstall anything first.
Where the containers run
- macOS (Apple silicon): in a small Linux VM on Apple’s
Virtualization.framework, started by litek8s itself. No Docker Desktop,
no Lima, no sudo.
$HOME,/private/tmpand/private/var/foldersare shared at the same paths, so-v $PWD:/appand Testcontainers’ temp files work, and published ports listen on127.0.0.1and::1. With Rosetta installed,--platform linux/amd64images run. - Linux (x86_64, aarch64): in a rootless sandbox of its own namespaces,
on crun, with no daemon, no
dockergroup and no setuid binary. It does not touch the host’s network interfaces, iptables rules or an installed Docker.
The numbers
Container operations, measured with bench/docker-bench.sh (the same
protocol against every engine, images pulled beforehand). Linux: an Ubuntu
24.04 arm64 guest (4 vCPU, 6 GiB), litek8s rootless against Docker 29.1 in
the same guest. macOS: an M3 Max, the litek8s VM against OrbStack.
| litek8s (Linux) | dockerd (Linux) | litek8s (macOS VM) | OrbStack | |
|---|---|---|---|---|
docker run --rm alpine true |
54 ms | 179 ms | 89 ms | 295 ms |
nginx run -d -p until it answers |
52 ms | 135 ms | 91 ms | 175 ms |
| compose up (postgres healthy, client ran) | 1.7 s | 2.0 s | 2.8 s | 3.1 s |
| build, no cache (python app) | 0.05 s | 0.37 s | 0.74 s | 0.61 s |
| build, all cached | 0.02 s | 0.15 s | 0.48 s | 0.28 s |
| memory, idle | 35 MiB | 113 MiB | ||
| memory, postgres + nginx running | 64 MiB | 149 MiB |
Builds are litek8s’s own builder (DOCKER_BUILDKIT=0). Notice that on
macOS OrbStack builds faster. The Linux memory column is litek8s’s whole
sandbox, Kubernetes API server, controllers and node agent included,
against dockerd + containerd + the containers. OrbStack’s memory cannot be
separated from its other machines, so it is not compared, and Docker
Desktop was not benchmarked at all.
And the Kubernetes side, from litek8s dev up to a Ready node, against
kind and k3d (Numbers):
| litek8s | kind | k3d | |
|---|---|---|---|
| Linux (Ubuntu 24.04 arm64 guest) | 0.17 s | 31.6 s | 11.1 s |
| macOS (M3 Max; kind and k3d on OrbStack) | 1.28 s | 34.3 s | 10.4 s |
| Idle memory, Linux | 47 MiB | 526 MiB | 398 MiB |
What already works
compat/docker/run.sh runs 17 cases that pass on Linux x86_64 rootless and
in the macOS VM. Roughly: docker run with the usual flags (ports,
volumes, env, healthchecks, --restart, limits, --privileged), exec,
logs -f, cp, stats, user-defined networks with aliases,
host.docker.internal, named volumes, compose with healthchecks and
depends_on: condition: service_healthy, the devcontainer CLI that VS
Code and Cursor run, and Testcontainers with random ports, wait strategies
and its Ryuk reaper.
Real projects ran unchanged, in copies: a FastAPI + Postgres backend on
macOS with uvicorn hot reload of Mac edits (an edit reaches a watcher in
the container in 30-70 ms), and on Linux x86_64 a Prometheus + Grafana
stack, MySQL + Redis with restart: always, and a Rails app with MariaDB
built from its Dockerfile (5 min 10 s with litek8s’s builder, 6 min 28 s
through buildx’s BuildKit container).
One thing that works differently on purpose: Docker and OrbStack limit containers one by one, while litek8s puts every container of a compose project in one cgroup, so you can give the whole stack a budget:
services: api: labels: dev.litek8s.project.memory: 2Gi dev.litek8s.project.cpus: "2"When the stack hits it, the OOM killer acts inside the project, on the runaway service, and the rest of your machine never notices.
What is missing or different
This is the section to read before you uninstall anything.
- Not supported (yet):
docker push,commit,export/import,diff, swarm and services, plugins and other volume or network drivers, IPv6 on container networks,--init,--device, GPU, published UDP ports, the BuildKit API itself (buildx’sdockerdriver), anddocker update(accepted, changes nothing). - buildx needs
--load. If buildx is installed (Docker Desktop, OrbStack and most Linux packages ship it), plaindocker buildgoes through a BuildKit container and the image does not show up indocker images, nor for pods, without--load. UseDOCKER_BUILDKIT=0or a builder withdefault-load=true.docker compose up --buildloads either way. - Containers are not pods.
kubectl get podsdoes not list Docker containers. They share the image store and DNS with the cluster, not the object model. - Containers do not survive
litek8s dev down; restart policies bring them back ondev up. - Ports: published on
127.0.0.1only (and::1on macOS), never on other interfaces; rootless Linux cannot bind host ports below 1024. - Two engines, one shell: with Docker or OrbStack also running, check
which one you are talking to (
docker version --format '{{.Server.Platform.Name}}'sayslitek8shere). A port the other engine already publishes fails withport is already allocated. - macOS memory: Virtualization.framework never gives a VM’s memory back
to macOS once the guest has touched it. litek8s keeps the guest small
and grows it on demand, but after a big build the way to get memory back
is
litek8s dev down && litek8s dev up. - A young project: litek8s is 0.x; flags and on-disk formats may change between releases (Limitations).
Platforms
| dev clusters and the Docker API | |
|---|---|
| macOS, Apple silicon (13+) | yes, in a VM (Virtualization.framework) |
| Linux x86_64 / aarch64 | yes, rootless (kernel ≥ 5.11, cgroup v2) |
| macOS Intel, Windows | no (WSL2 untested) |
On Linux it needs crun, uidmap, passt (or slirp4netns), iproute2
and iptables; litek8s dev setup checks and says what is missing, and
Ubuntu ≥ 23.10 needs sudo "$(command -v litek8s)" dev setup once for
AppArmor. macOS needs nothing else.
Try it
brew install jairoFernandez/tap/litek8sor
curl -fsSL https://raw.githubusercontent.com/jairoFernandez/litek8s/main/install.sh | shThen litek8s dev up, point DOCKER_HOST at the socket, and run the
compose file you already have. Every flag and what each command supports
is in Docker API; the cluster side in
Dev clusters.