Appearance
Docker Compose
The default, and the one dqx generates most for.
What dqx writes
The whole stack: the app container, nginx, the database, and whatever services the project declares. It is generated from .dq/config.yml, so it is disposable — delete it, run dqx sync, get the same thing back.
Generated files live under .dq/ in a single-repository project, or beside the wrapper in a multi-repository one, and they are gitignored. The declaration is the only thing committed.
Ports
Nothing binds a fixed host port except the shared router, which owns 80 and 443. Everything else — the database, redis, mail — is published on an ephemeral port that changes on every up. That is deliberate: two projects running together never collide.
dqx info and dqx db --url print the ports that are true right now.
What it costs
File access is a bind mount, which on macOS is the slow path. A large
composer installor a Vite build feels it.Try OrbStack before switching backend. Same engine, same commands, nothing in the project changes. Measured on one machine, both engines, the same 48,108-file project tree, warm:
Docker Desktop OrbStack walking the tree 1,780–1,856 ms 401–430 ms 4.2× reading 3,000 PHP files 450–465 ms 88–89 ms 5.1× duovervendor1,422–2,355 ms 341–349 ms 4.2× and better This is not the old comparison against the FUSE mount Docker Desktop used to have: that machine runs Docker Desktop with VirtioFS and the Virtualization framework, the same modern base OrbStack builds on. What OrbStack adds is its own caching and tuning on top of it.
The ceiling is the boundary itself: the same tree inside a VM, with no mount to cross, walks in tens of milliseconds. Both engines pay for going between macOS and Linux; one pays less. If that remaining gap is still your bottleneck, DDEV is the answer — at the cost of much slower starts.
What the same machine does with the heaviest project in one fleet — four sites, code on a bind mount, PHP and MySQL and Elasticsearch and redis:
Docker Desktop OrbStack dqx up31.7–31.9 s 31.5–31.7 s the same dqx down2.3–2.4 s 3.1–3.3 s Desktop, by a second a warm page 26–34 ms 25–37 ms the same the first page after a restart 426–458 ms 228–301 ms 1.8× composer dump-autoload -o4.8–6.6 s 2.6–4.7 s 1.4–1.8× So the syscall gap is real but it is not the whole story.
upis dominated by healthcheck intervals, which belong to the compose file rather than the engine, and a warm page is served out of opcache without touching the mount at all. Where the gap shows is everything cold: the first request after a restart or a cache clear, and composer.There is nothing to tell dqx. Which application provides
dockeris read from the docker context, sodqx upstarts OrbStack withorb startand Docker Desktop withdocker desktop start, anddqx kill --allasks the right one for its memory back. A setting in dqx would be a second place that can be wrong while the machine says otherwise.dqx doctoranddqx info --jsonboth say which one you are on.
Choosing between OrbStack and Docker Desktop
The switch is docker's own, not dqx's:
bash
docker context use orbstack # to OrbStack
docker context use desktop-linux # back to Docker Desktop
docker context show # which one is currentWhat comes with you, and what does not
The two are separate virtual machines, and that splits three ways rather than one:
- Containers and images do not move. They are rebuilt or re-pulled on the other side. Measured at the moment of one switch: 19 containers on Docker Desktop, 0 on OrbStack.
- Named volumes do not move. Dump them with
dqx snapshotbefore you switch or they are gone. In this fleet that is 1 project in 32. - Bind-mounted data directories do come with you. They live in the project, on the Mac, and neither VM owns them. The other 31 keep their databases across the switch, untouched.
For almost every project here, then, the switch is:
bash
docker context use orbstack
dqx up # re-pulls the images, reuses the data already on diskA floating tag is not the same image on both engines
Each engine keeps its own image cache, so redis:alpine is whatever that engine happened to pull last. This bites in practice: OrbStack's redis 8.10.1 wrote dump.rdb in RDB format 15, and Docker Desktop's older redis:alpine would not start on it —
# Can't handle RDB format version 15
# Fatal error loading the DB, check server logs. Exiting.The data was fine. The reader was older. docker pull redis:alpine on whichever engine lags behind settles it, and pinning the tag stops it happening again — this fleet carries 44 floating references (29 × mailpit:latest, 13 × redis:alpine, 2 × redis-stack-server:latest).
- Docker Desktop holds memory it does not hand back until it restarts.
dqx memoryshows what each project is using and what the VM is holding beyond that.
What it does better than the rest
- Several sites in one project are ordinary here, each with its own domain, docroot and PHP version if it needs one.
- Complete isolation. Two projects on different PHP versions with different extensions is a configuration, not a conflict.
- It is the same everywhere. The one backend that behaves identically on a colleague's Linux machine and on CI.