Docker without Docker Desktop: the compatibility path
ArcBox's container story is not that it has replaced Docker with a second, look-alike engine. The subject here is the boundary between a familiar Docker client and a guest dockerd running inside ArcBox's System VM. This chapter follows a request shaped like docker run through that boundary, then explains why ArcBox handles some routes itself while forwarding others. The result is a practical way to judge the project's “drop-in” claim: ask which part of the Docker contract ArcBox preserves, which part the guest engine owns, and how the runtime knows the path is ready.
The request crosses two contracts
From a user's point of view, the entry point is deliberately ordinary. The Docker CLI connects to an ArcBox-managed Unix socket. A Compose file, a build context, or an interactive exec session can use the same Docker-shaped interface it would use with another local engine. The fact that the engine is elsewhere matters only when we trace the request: the host socket is a front door, not the place where containers actually run.
The first host-side component is a router that normalizes the Docker API. Docker clients commonly put a version prefix such as /v1.51 in the request path. ArcBox removes that prefix before matching routes, while retaining the original URI for forwarding. This lets one host route table accept versioned and unversioned clients and lets the guest engine receive the request shape it expects. The router is not negotiating a new API with every client; it is making the host boundary tolerant while leaving engine-level version behavior to dockerd.
The router then asks a more consequential question: does this endpoint require ArcBox-specific behavior? Container creation, lifecycle changes, selected network operations, and build-related actions can have host-side consequences. They may require path normalization, registration of a container's network or DNS state, port-forwarding setup, or coordination with the System VM lifecycle. ArcBox handles those selected paths locally. The local handler can perform the host-side work and then use the guest proxy for the part that still belongs to Docker.
Every other endpoint takes the fallback path. Rather than recreating the whole Docker Engine API in Rust, ArcBox forwards ordinary requests to the guest's dockerd. The guest engine remains responsible for its own container, image, volume, and much of its network behavior. This division is the central compatibility decision. ArcBox owns the effects that cross the Mac/System-VM boundary; Docker owns the engine semantics that can stay inside the guest.
Readiness is a separate contract from routing. Before a request is forwarded, ArcBox's core runtime ensures that the System VM is ready, the guest agent has prepared the container runtime, and the Docker endpoint reported by the guest matches the port the host is configured to use. The Docker layer then verifies readiness at the HTTP level with a real /_ping request. A VM can be running while dockerd is still starting, so treating “the VM process exists” as “Docker is ready” would turn a normal startup interval into a stream of misleading connection errors.
The readiness result is cached for the selected utility-VM role. A burst of concurrent Docker requests does not make every caller repeat the slow preparation path; one caller verifies while the others wait for the state to change. If a transport or proxy request fails, the role's cached readiness is invalidated. The next request must prepare the runtime again and obtain another successful Docker response. In this design, a cache is not a permanent assertion. It is a claim that remains valid only until the transport provides evidence that the guest path has changed.
The transport between the host proxy and the guest is vsock. That keeps the guest engine off the host's ordinary TCP surface while giving the proxy a stream it can use for HTTP forwarding. The user still sees a local Unix socket, but the container process is created on the other side of the System VM boundary. The distinction becomes important for state and failure: a host-side route may need to clean up a port forward or DNS record even when the guest operation has ended, and a guest restart can invalidate a host proxy's pooled connections.
The sequence has two forks, and they solve different problems. The first fork is about ownership: ArcBox-owned endpoints need host-side effects, while ordinary endpoints can pass through. The second is about protocol behavior: an ordinary request, a large streamed upload, and an HTTP upgrade cannot safely share the same connection lifecycle.
Protocols reveal the boundary
Ordinary forwarding can use a reusable HTTP/1.1 client connection to the guest. The response body streams back to the Docker client rather than being fully collected by the host. Reuse is safe only when the request and response have reached the end of their ordinary HTTP exchange. If a body is dropped early or a transport fails, the connection must not be returned to a pool in a state that contains unread bytes from one request. The proxy's pooling logic is therefore a correctness boundary, not merely a latency optimization.
Uploads have a different shape. A Docker build context or image load can be large and should move through the proxy as a stream. The proxy can return the guest response while the incoming body is still being drained, so the upload path owns a dedicated connection lifecycle rather than pretending it is a normal small request. This avoids mixing partial request bodies with the next logical HTTP exchange and lets ArcBox keep the memory cost of a build context proportional to the stream rather than to the whole archive.
An HTTP upgrade changes the contract again. Attach, interactive exec, and BuildKit-style sessions begin as HTTP but become byte tunnels after the 101 Switching Protocols response. Once the upgrade succeeds, the proxy must take ownership of the raw connection and forward bytes until the session ends. Sending such a connection back into an ordinary pooled HTTP client would confuse protocol state and could corrupt later requests. The proxy is split by transport behavior because the bytes, not the product label, determine what safe forwarding means.
The same boundary appears in activity tracking. A Docker request is not always a short unary call. Image pulls, builds, log streams, and interactive sessions can keep the guest busy for a long time. ArcBox's proxy state has a lifecycle activity hook into the System VM manager. A short request records activity; a long operation can hold an activity scope until the operation ends. That prevents the runtime's idle policy from treating an in-flight transfer as an idle VM and shrinking resources underneath it. The host therefore observes Docker traffic not only to route it, but also to keep shared infrastructure aligned with the work it is carrying.
Host-side behavior is especially important for networking. A container may have an address inside the guest, but publishing a port to the Mac requires a host listener and a forwarding rule. ArcBox records that relationship so it can remove the rule when the container stops, is removed, or disappears through a path that did not call an explicit stop operation. DNS aliases need similar ownership tracking. A guest engine can know that a container exists; only the host-side runtime knows which host listener, route, or local name it created for that container.
This is why compatibility should not be evaluated only by whether a simple command prints output. The visible Docker API has at least three layers of behavior: the client-facing route shape, the guest engine's container semantics, and ArcBox's host integration. A command may be accepted by the guest engine yet still depend on the host for published ports, names, mounted paths, or cleanup. Conversely, ArcBox can preserve the Docker command while changing where the engine runs and how readiness is established.
Consider a container with a published web port. The guest engine can create the container and assign its guest-side network state, but the user expects a connection from the Mac. ArcBox's host handler must translate the requested binding into a host listener, remember which container owns it, and remove it when the container disappears. A container that exits through an ordinary stop call and one that vanishes through --rm, an out-of-memory event, or a guest-side failure can therefore require the same cleanup reconciliation. The API response is Docker-shaped; the lifecycle underneath it includes state that only ArcBox can see.
A build exposes a different ownership boundary. The client sends a potentially large context, the proxy streams it to the guest, and BuildKit or dockerd performs the engine work. ArcBox does not need to understand every instruction in that build to preserve the workflow, but it does need to keep the transfer's connection, guest readiness, and System VM activity coherent until the operation finishes. This is the pattern behind the compatibility layer: leave engine semantics where they already belong, and add host behavior only where the boundary makes it necessary.
The repository lists container operations, images, builds through BuildKit/Buildx, Compose, port forwarding, bind mounts, named volumes, interactive execution, and Kubernetes integration as working surfaces. Those are meaningful signs that the compatibility layer is designed around real workflows rather than a minimal run demonstration. But the evidence remains a repository-level product and test claim. A selective host router is not a complete reimplementation of Docker Engine, and pass-through behavior inherits the guest engine's version, configuration, and edge cases. “Drop-in” should therefore mean “existing Docker-shaped workflows can use ArcBox through this boundary,” not “every Docker API detail has a new implementation with identical behavior.”
The design also clarifies the relationship with Docker Desktop and OrbStack. ArcBox is not trying to make the Docker CLI understand a novel API. It keeps the CLI at the edge and moves the compatibility work into the daemon. That makes migration easier for scripts and Compose files, but it places more responsibility on ArcBox to maintain the socket lifecycle, proxy correctness, guest readiness, and host-side state. The product feels simple precisely because the complexity has been concentrated behind the interface.
A restart exposes the cost of that concentration. The persistent Docker data image can survive a System VM restart, but the guest's running service and the proxy's cached connections do not automatically remain valid. ArcBox tracks a VM restart generation so the Docker layer can discard stale readiness and pooled transport state before a new request reuses it. Stored images and container metadata are one kind of continuity; an active HTTP stream or running container is another. The compatibility path has to distinguish them or it will either leak host state or claim readiness too early.
The local API's security posture is similarly specific. The Docker socket is a local integration point, and the guest engine is reached through the System VM rather than an exposed remote TCP listener. That reduces the number of network surfaces a default installation needs, but it does not turn Docker commands into an untrusted operation on the host. The local user controls the daemon, the host owns the VM, and the privileged helper handles a narrow set of system mutations. API compatibility and trust isolation are separate questions.
ArcBox's container path is therefore best understood as an adapter with a clear division of labor. The Docker client supplies a mature interaction model. The guest dockerd supplies engine behavior. ArcBox supplies the bridge: route normalization, host lifecycle side effects, guest readiness, vsock transport, connection-type-specific forwarding, and cleanup. That bridge is what lets one System VM support a familiar container workflow without asking the user to learn where the process actually runs.
The next boundary asks whether the same bridge is strong enough for untrusted workloads. Containers make an engine feel local; ArcBox's sandbox tier makes a disposable microVM feel programmable. That requires a different lifecycle, a different guest agent, and a more explicit answer to what survives when the client, VM, or network connection goes away.
References
- Docker compatibility layer
- Docker API router
- Docker API server
- Guest Docker readiness
- ArcBox core runtime
- ArcBox core overview
- System VM lifecycle manager
- Typed daemon startup pipeline
- Daemon Lifecycle
- Connect API service registration
- Sandbox control service
- Sandbox process service
- Sandbox filesystem service
- Sandbox API integration reference