Appearance
PHP images
A docker site runs in an app container, and the container runs an image. A new project gets one of the dqx-php images: PHP, Composer, Node and the tools a Laravel checkout needs, built and tested together.
yaml
sites:
- domains: { primary: crm.northwind.local.example }
image: dolphiq/dqx-php85-alpine-slim:1.0The dqx-php images
The name says what is inside: dolphiq/dqx-php<major><minor>-<distro>-<flavour>:1.0.
| Part | Values |
|---|---|
| PHP | 8.2, 8.3, 8.4, 8.5 |
| distro | alpine (default), debian |
| flavour | slim (default), complete |
slim is the default. complete adds imagick, pdo_pgsql and pgsql to the extensions, and zsh with oh_my_zsh and zsh_autosuggestions to the tools. dqx image info <image> lists both, so you can compare before you choose.
The tag is :1.0, and it floats. Patches to PHP, Node and the tools arrive under the same tag; the distro and the Node major stay. dqx images --update pulls the newer image and dqx up recreates the containers on it. dqx never writes :1.0.0 (it would never move) or :1 (it would move to a new Node major).
New projects start on PHP 8.5, so dqx init writes dolphiq/dqx-php85-alpine-slim:1.0. PHP 7.4 and 8.1 have no dqx-php image: they keep dolphiq/php74-composer-npm:2 and dolphiq/php81-composer-npm:3.
An existing project keeps what it has. A site that names php: '8.3' and no image still resolves to dolphiq/php83-composer-npm:3; dqx does not change an image behind your back. dqx init --from an older .dq/config.yml with php and no image is a new project, though, and gets a dqx-php image. The next section and moving an existing project show how to choose the change.
Alpine, or Debian
Alpine is the default: a small image that builds fast. It uses musl, not glibc, and a few programs only ship for glibc. For those, use Debian.
Say it in .dq/config.yml, for the project (dqx 1.77+; it is only written when it is not alpine):
yaml
distro: debianor when you create the project:
bash
dqx init --distro debianWithout --distro, dqx init (when it is given a target, --from or --domain) looks at the checkouts that exist and gives Debian to a site that needs glibc, and says why:
text
distro debian (needs glibc: spatie/browsershot, puppeteer)The scan reads composer.lock (every package composer installs, so barryvdh/laravel-snappy brings in knplabs/snappy and spatie/laravel-pdf brings in spatie/browsershot; composer.json when there is no lock), the dependencies and devDependencies of package.json, and your own PHP sources, so a fresh clone with nothing installed is fine, and vendor and node_modules are skipped. On the npm side only puppeteer also counts when another package pulls it in (it downloads Chrome on install); @playwright/test brings playwright, but dqx runs that suite in its own browser service. It looks for:
- composer:
spatie/browsershot,knplabs/snappy,h4cc/wkhtmltopdf-amd64,h4cc/wkhtmltoimage-amd64,mikehaertl/phpwkhtmltopdf - npm:
puppeteer,playwright,@sparticuz/chromium - your own PHP: a use of
GLOB_BRACE, which musl does not have
--distro on a site with PHP 7.4 or 8.1 is ignored, with a message: those images have no other distro to pick.
Switching later
bash
dqx image switch --distro debian # a dry run: what would change
dqx image switch --distro debian --write # rewrite .dq/config.yml and run dqx sync
dqx up --build # build the new containerdqx image switch without --write only prints the plan. With it, dqx rewrites the image lines in .dq/config.yml (your comments stay), runs the sync, honours --runtime, and prints Rebuild with: dqx up --build. --site <domain> moves one site only. When every site is already on the distro, --write still records distro: for the project.
Without --distro, a site already on dqx-php keeps its distro. A site on an old house image goes to the project's distro (Alpine when it states none), or to Debian when its checkout needs glibc. A dqx-php image on a tag from before :1.0 (:0.1, :0.2) moves to :1.0 of the same image, distro and flavour kept; the proposals say <site> runs <image>; :1.0 is out. A site on PHP 7.4 or 8.1 is skipped (no dqx-php image for PHP 8.1) and the other sites are still moved.
When the libc changes, reinstall node_modules. Native packages (esbuild, rollup, sharp, the Tailwind engine) are built for one libc. After a move between Alpine and Debian, or from an old glibc image to Alpine, the old ones fail with Cannot find native binding. dqx says so when the switch crosses libc; run dqx npm ci (or your install command) after the rebuild.
dqx only moves images it knows. A site on an image of your own is refused with the reason, and nothing is changed.
What is inside
bash
dqx image info dolphiq/dqx-php85-alpine-slim:1.0prints the image's PHP, Node, Composer, extensions and tools, read from the image's label: from the local image when this machine has it, otherwise from the registry without pulling. --json prints the same as JSON. What Docker Hub said about a tag is kept on disk for a day (under ~/.local/share/dqx/image-contents), so neither this nor the dashboard asks it every time; --refresh asks again, and dqx images --update renews the entry of each image it pulls.
Inside a project, dqx image info shows each app container: its image, what the image declares, and drift, when the running container is older than the image its tag points at now (an older pull of :1.0). dqx images --update, then dqx up, closes the gap.
In the container, the image states itself in /etc/dqx-image.json, read from the binaries when it was built, and dqx-image-info prints it.
In the dashboard, a project's app service chips say what their image holds: app, and each container dqx adds for another PHP version (app83). Click one, also on a dashboard started with --read-only, for the configured image, its PHP, Node, Composer and distro, the first eight extensions (+N more for the rest) and a warning when the container is older than its image. A site with phpExtensions runs an image dqx builds on this machine: the popover names the configured image and adds built locally with: <extensions>, and those extensions never count as drift. A dolphiq image links to its tag on Docker Hub; a local or foreign-registry image has no link. all image details opens an overlay with everything the label and the running container say: each extension with its version, the tools and packages, PHP, Node and Composer as the image declares them beside what runs, the drift lines, and where the label was read (the image on this machine, or Docker Hub, kept for a day). For an image without the label it says "no dqx contents label", for an image on another registry "image contents not checked (not on Docker Hub)", and while it loads "reading image...", or "image info unavailable" when that fails. Each open asks again, so a switched image shows without a reload.
Moving an existing project
dqx proposes the move where you already look:
dqx doctoradds a!note in an images sectiondqx adopt --writeprints "Images the sites could move to:"dqx images --outdatedprints<path>: can move to dqx-phpfor each project
A proposal keeps the site's PHP version, and picks Debian when the glibc scan says so. A site already on a dqx-php Alpine image that needs glibc gets the Debian proposal too. Then:
bash
dqx image switch # read the plan
dqx image switch --write
dqx up --buildMove one site first with --site, check it, then the rest.
Your own image
image: on a site takes any image. dqx does not change it, and it does not refuse it:
yaml
sites:
- domains: { primary: crm.northwind.local.example }
image: registry.northwind.local.example/crm-app:4What dqx expects of it, from what it generates:
- PHP-FPM on port 9000. nginx sends PHP to
app:9000. The container's healthcheck runsphp-fpm -t; that check is the dqx-php images' own, not something dqx adds. phpandcomposeron the PATH, becausedqx exec,dqx composeranddqx phprun them in the container, and a shell (sh) for the commands and hooks dqx runs there.- The checkout at
/var/www. The stack mounts it there and sets it as the working directory. - Root's home for caches. The stack mounts the Composer cache and auth file under
/root/.composer, and~/.ssh/id_rsaas/root/.ssh/id_rsa_host. An image that runs as another user does not see them. - PHP configuration in
/usr/local/etc/php/conf.d/, where dqx mountsphp-ini-overrides.iniand, on images that are not dqx-php, the Xdebug ini. - If a site uses
phpExtensionsorpackages, a package manager (apk,apt-getordnf) for the image dqx builds on top of yours.
What you lose without a dqx-php image:
dqx image infohas no label to read, so no contents and no drift check- the app chip's image popover in the dashboard has nothing to show
- Xdebug: on the dqx-php images dqx mounts its settings ini at
conf.d/zz-dqx-xdebug.iniand switches the mode throughXDEBUG_MODE, which survives a recreated container. On any other image dqx copies an ini file into the container instead, which a recreated container loses until the nextdqx xdebug dqx image switchand the proposals leave the site alone
The better option is to start from a dqx-php image and add what you need:
dockerfile
FROM dolphiq/dqx-php84-alpine-slim:1.0
RUN apk add --no-cache ghostscriptYou keep the labels, the info, the drift check and Xdebug, and add only your packages. Point the site at the result with image: (or compose.app.build).
For only a missing PHP extension, do not build anything. Declare phpExtensions and dqx writes the Dockerfile for you; see PHP extensions and packages.