Appearance
The daily commands
Every command works from anywhere inside a project - dqx walks up to find .dq/config.yml, the way git finds a repository. Standing in a site's directory also picks that site.
Starting and stopping
dqx up | Start every site of the project, and say which one failed rather than reporting success |
dqx down | Stop this project |
dqx kill | Stop every container on every backend, the router included, and free 80 and 443; the runtimes keep running. --mine leaves containers of no dqx project alone |
dqx kill --all | That, and the runtimes too, giving their memory back |
dqx update | Pull the images this project declares |
dqx up also brings up the shared router if it is not running, reissues the certificate when it does not cover a domain yet, and frees a port another project is holding - naming what it stopped. See the shared router for what it is and why the ports move.
Working inside a project
A site can be named by its domain or by its directory
A site's checkout is not its domain: one project here serves register.example… out of www/sites.example…. Both work, and so does a prefix:
bash
dqx shell register.example.local.dolphiq.eu
dqx shell sites.example.local.dolphiq.eu
dqx shell sitesStanding inside www/<checkout> needs no argument at all - dqx uses the directory you are in, and now does so in a project with no .dq/config.yml too.
dqx shell | A shell in the site, in the directory you are standing in |
dqx shell --site <domain> | A shell in a named site, from anywhere in the project |
dqx exec -- <cmd> | One command in the site |
dqx composer / npm / artisan / php | The tool, in the right container and directory |
dqx test | The project's test suite |
dqx run <name> | A command the project declared in commands: |
dqx open | The site, in your browser |
The dev server, and its port
A site whose assets come from Vite needs the dev server running inside the container, on the port this project's stack publishes:
bash
dqx dev # the site's dev server, on this project's own port
dqx dev --site app.acme.dqx # when the project has more than oneThe port matters and it is why this is a command rather than npm run dev. Every project owns a block of four ports, either from a pinned VITE_PORT or derived from the project itself, and its stack publishes them - so several projects, and up to four sites within one project, can run a dev server at the same time. Measured on this machine: two projects both wanted 5173, one of them with that number hardcoded in its own compose file, so only one dev server could ever start.
dqx dev passes --host --port <the site's port> --strictPort, because Vite otherwise binds its own 5173 - and then nothing listens on the published port, so the page loads while every asset fails with net::ERR_CONNECTION_REFUSED - or, with the port taken, quietly moves to one nothing publishes. It also sets VITE_PORT to the same number inside the container, for a config that reads it.
dqx dev stops the dev server in the container when it is itself stopped: Ctrl-C, a closed terminal, or a task runner's stop. docker exec passes no signal into the container without a terminal, so before this a stopped dqx dev left Vite listening.
A dev server still running on that port anyway - one started by hand, or a dqx dev that was killed outright - is named before anything starts, with the way to stop it:
text
✗ port 5220 is already taken by a dev server running for 27:57 (pid 1054): node …/vite --host --port 5220 --strictPort
stop it with Ctrl-C where it runs, or replace it: dqx dev --replacedqx dev --replace stops that server and starts a new one.
A project whose stack predates the shared router keeps those ports too: dqx serves such a project through an override that takes its fixed host bindings away, and that override now keeps the dev server's, because a browser is told that number by the application.
Two things a project's own vite.config.js decides, which dqx cannot:
- https. dqx mounts the project's certificates at
/etc/ssl/certs/local; a config that reads them serves the dev server over https, and the asset URLs then match the page. - the URL the browser is told. Laravel's Vite plugin writes the address Vite bound into
public/hot, which inside a container is not an address a browser can use. A config that setsserver.hmr.hostto the site's domain fixes that, and keeps the port Vite actually runs on.
A port in the config wins over --port
--port moves the server, not the URL. A server.origin like https://app.acme.dqx:5173, or a server.hmr.port / clientPort, still sends the browser to that number, so the hot file says 5173 while Vite answers on the project's own port. dqx dev and dqx doctor both say so, dqx sync says it right after pinning the port, and dqx adopt says it in its preview against the port the project will get. None of them edits the config: it is the application's code.
text
vite ! app.acme.dqx: vite.config.js sends the browser to port 5173 (server.origin), but Vite runs on 5220, so every asset is refused
remove server.origin, or build it from process.env.VITE_PORT, which `dqx dev` setsRemoving server.origin is usually enough: with server.hmr.host set, Laravel's plugin builds the URL from that host and the port Vite bound.
Until then, dqx dev starts Vite through an overlay, so the project works without anyone editing it first. The overlay is a small config in node_modules/.dqx/ that loads the project's own and moves only the pinned ports to the one Vite runs on; host, https and plugins stay as the project wrote them. It is never committed, and it is removed again once the config no longer needs it:
text
! vite.config.js sends the browser to port 5173 (server.origin); running Vite with a dqx overlay that moves it to 5220
node_modules/.dqx/vite.config.mjs - remove server.origin from vite.config.js to make this unnecessary, or run with --no-overlayThe overlay reaches Vite through --config, so it is used only where the dev script is Vite itself. A script that chains or wraps it (concurrently …) gets the warning instead. --no-overlay starts Vite with the project's own config.
A project generated from the fleet's own recipes carries both; Laravel's default config carries neither. Dev server ports shows a config that does both, for Vite, Laravel Mix, webpack-dev-server and Gulp.
The database
dqx db | Open the client |
dqx db --url | The URL, for TablePlus or a dump |
dqx db:import <file> / db:export | In and out |
dqx snapshot / dqx restore | The database and the storage directories |
The published port is this project's own rather than 3306, so two projects never fight over one - and it stays put, so a saved connection keeps working. dqx db --url prints it, and ports: asks for a particular one.
Seeing what is going on
dqx dashboard puts all of this in a browser: every project, what is running, and start and stop buttons. See the dashboard.
dqx info | Every URL, port and connection this project has |
dqx info --json | The same, for a script or an agent |
dqx list | Every project on the machine, on every backend (below) |
dqx logs [service] | Tail logs |
dqx doctor | What is broken here, and what to do about it |
dqx memory | What each project is costing, and what the VM holds beyond that |
What dqx list looks like
One row per checkout, with every domain that checkout answers to:
txt
KEY PROJECT CHECKOUT URL BACKEND STATUS
acme Acme Intranet acme-intranet.dqx https://acme-intranet.dqx docker stopped
hb Harbor Books admin.harbor-books.dqx https://admin.harbor-books.dqx docker running
api.harbor-books.dqx https://api.harbor-books.dqx docker running
harbor-books.dqx https://harbor-books.dqx docker running
https://www.harbor-books.dqx
hb-pr-1284 Harbor Books harbor-books.dqx https://harbor-books.pr-1284.dqx docker running
nwd Northwind northwind https://northwind.dqx docker stopped
nwd-acme-6479-invoice-export Northwind northwind.dqx https://northwind.acme-6479-invoice-export.dqx docker stopped
old-site old-site old-site.dqx https://old-site.dqx docker stopped
tp Tidepool tidepool https://tidepool.dqx docker stoppedGenerated
This output is rendered by the same function the command uses, against the fictional fleet dqx ships for its documentation - so it cannot drift from what the command prints. bun run docs:output rewrites it.
Xdebug
bash
dqx xdebug on # and off, and `dqx xdebug` to see whichPer developer and gitignored, so it never lands in somebody else's checkout, and dqx up re-applies your choice.