Help:User journeys/Extending Canasta images with custom Docker builds
Journey info · Platform: Docker Compose · Time: ~30 minutes
This journey is a worked example of extending Canasta's stock container images with your own Docker build, using docker-compose.override.yml. The anchor case: adding a PHP extension to the web container so a MediaWiki extension can reach an external MongoDB server. The same pattern extends any service in the stack (Elasticsearch plugins, extra system packages, additional PHP extensions). It is one path, not the only one — for the conceptual material on the override file and on extensions, see Help:Orchestrators and Help:Extensions and skins.
Prerequisites
- A working Canasta installation on the Docker Compose orchestrator (this journey does not apply to Kubernetes).
- Shell access to the host, and the ability to run
dockerandcanasta. - The instance directory (the folder created by
canasta create, containing.envanddocker-compose.yml). All paths below are relative to it. - Knowing which extension or capability you are adding and what it requires. For the MongoDB example, the requirement is the
php-mongodbPHP extension plus the MediaWiki extension that uses it. - A place to keep the files you add (
Dockerfile.custom, the override) under version control — see the backup caveat below.
How the override extends an image
The stock web service in docker-compose.yml is defined roughly as:
services:
web:
image: ${CANASTA_IMAGE:-ghcr.io/canastawiki/canasta:latest}
Docker Compose automatically merges a file named docker-compose.override.yml (in the same directory) on top of docker-compose.yml. To extend the web image you add a build: section and point image: at a new, unique tag of your own. Compose then builds your Dockerfile and runs the resulting image in place of the stock one.
Two rules make this reliable:
- Always give your custom build a unique image tag — e.g.
<instance-id>:custom. Never reuse the base image'sname:tag(ghcr.io/canastawiki/canasta:latest). A Compose service that has bothimage:andbuild:only builds when no image of that name already exists locally; if you reuse the stock tag, Compose finds the already-pulled stock image and silently runs it instead of your build, and a laterdocker compose pullcan overwrite your build from the registry. A unique tag is written only by your build, so nothing else clobbers or shadows it. - Rebuild before you restart.
canasta restartrunsdocker compose up -d, which never rebuilds an image that already exists — it reuses the cached tag. After any change to yourDockerfileyou must rundocker compose build <service>(add--no-cachewhen in doubt) beforecanasta restart, or you will keep running the old image.
Phase 1: Write the custom Dockerfile
Canasta's image is Debian 12 with distribution PHP 8.2 installed from apt — it is not built on the official php Docker image. That means the familiar pecl install ... && docker-php-ext-enable ... recipe does not apply here; those helpers do not exist. On Canasta you install PHP extensions the Debian way: the php-* apt package (which auto-enables itself via phpenmod), or, for an extension with no Debian package, a manual PECL build followed by phpenmod.
Create Dockerfile.custom in the instance directory. The ARG/FROM pair lets the build inherit whatever Canasta version your instance is pinned to (passed in from .env in Phase 2), so your customization rides along across upgrades instead of freezing you on one release:
ARG CANASTA_IMAGE=ghcr.io/canastawiki/canasta:latest
FROM ${CANASTA_IMAGE}
# Add the PHP MongoDB extension so a MediaWiki extension can reach MongoDB.
# Debian packaging auto-enables the module; no docker-php-ext-enable here.
RUN apt-get update \
&& apt-get install -y --no-install-recommends php-mongodb \
&& rm -rf /var/lib/apt/lists/*
If the extension you need has no Debian package, build it with PECL instead (note phpenmod, not docker-php-ext-enable):
RUN apt-get update \
&& apt-get install -y --no-install-recommends php-pear php8.2-dev build-essential libssl-dev pkg-config \
&& pecl install mongodb \
&& phpenmod mongodb \
&& apt-get purge -y php-pear php8.2-dev build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
Phase 2: Wire it into docker-compose.override.yml
Create or edit docker-compose.override.yml in the instance directory. Override only the keys you need; everything else is inherited from docker-compose.yml:
services:
web:
image: <your-instance-id>:custom
build:
context: .
dockerfile: Dockerfile.custom
args:
CANASTA_IMAGE: ${CANASTA_IMAGE}
image:is your unique tag — replace<your-instance-id>with something specific to this instance. This is the rule-1 safeguard above.args.CANASTA_IMAGE: ${CANASTA_IMAGE}forwards the instance's pinned image (defined in.env) into the Dockerfile'sFROM, so a custom build always layers on top of the correct Canasta version.
Choosing the build context
context: . (the instance directory) is fine when your Dockerfile only adds packages — a RUN apt-get install …, like the MongoDB example above. It needs no files of its own.
If your Dockerfile COPYs files into the image (a script, a config snippet, an asset), put the Dockerfile and those files in a dedicated subdirectory and point the context at it instead:
build:
context: ./custom-build
dockerfile: Dockerfile.custom
A COPY can only read files that live inside the build context, so a self-contained subdirectory keeps the context minimal and ensures those files travel with the instance: canasta backup captures a subdirectory build context in full. With context: ., backup captures the Dockerfile but not arbitrary files it copies from elsewhere in the instance directory — so reserve context: . for builds that don't COPY anything. See the note on backups below.
Confirm CANASTA_IMAGE is set in .env (it normally is):
grep '^CANASTA_IMAGE=' .env
Phase 3: Build and apply
Build the custom image, then restart the instance so the new image is used:
docker compose build web
canasta restart --id <your-instance-id>
docker compose build reads the override automatically and produces your <your-instance-id>:custom tag; canasta restart recreates the containers with it. Remember rule 2: if you later edit Dockerfile.custom, re-run docker compose build web (with --no-cache if a cached layer is hiding your change) before restarting — canasta restart alone will not pick up Dockerfile changes.
Phase 4: Validate
Confirm the PHP extension is loaded inside the running web container. Use canasta maintenance exec rather than reaching for docker directly — it targets the right container for the instance and is the supported way to run a command inside it:
canasta maintenance exec --id <your-instance-id> -- php -m | grep -i mongodb
You should see mongodb in the module list. To confirm the running web image is your custom tag and not the stock one, a read-only docker compose ps (run from the instance directory) is fine — Canasta has no command that reports a service's image:
docker compose ps web --format '{{.Image}}'
This should print <your-instance-id>:custom. Finally, enable the MediaWiki extension that uses the new capability — the PHP extension alone does nothing until a MediaWiki extension calls it. A bundled-in-the-image extension still needs its enable line; add it the normal way (see Help:Extensions and skins), typically a wfLoadExtension( '<ExtensionName>' ); in your settings, then verify it appears on Special:Version.
Cleanup / teardown
To revert to the stock image:
- Remove the
web:block (or just theimage:/build:keys) fromdocker-compose.override.yml. If the override now has no remaining content, delete the file. - Disable the MediaWiki extension you added (remove its
wfLoadExtensionline). - Recreate the container on the stock image:
canasta restart --id <your-instance-id>
- Optionally remove the now-unused custom image to reclaim disk:
docker image rm <your-instance-id>:custom
- Delete
Dockerfile.customif you no longer need it.
Order matters: edit the override before restarting, so the restart lands on the stock image; remove the custom image last, after nothing references it.
Production considerations
- Keep your build files in version control.
Dockerfile.customand the override are the only record of how your image differs from stock; treat them like code.canasta backupcaptures them too (see below), but version control is still where they belong. - Pin or review the version your
FROMinherits. BecauseCANASTA_IMAGEdrives the base, your customization follows the instance through upgrades — which is usually what you want, but rebuild and re-test after a Canasta upgrade so a new base doesn't break your added packages. - Keep the Dockerfile minimal. Each added apt package is attack surface and image weight. Install only what the extension needs, and purge build-only dependencies in the same
RUNlayer (as the PECL example does). - The same pattern applies to other services. To add Elasticsearch plugins, override the
elasticsearchservice the same way — see the Custom Elasticsearch plugins section of Help:Extensions and skins.
A note on backups
canasta backup captures your docker-compose.override.yml and the build inputs it references, alongside config/, extensions/, skins/, images/, public_assets/, .env, and my.cnf, so a backup restored onto a fresh host can rebuild the custom image. What "build inputs" means depends on your context:
- Subdirectory context (
context: ./custom-build): the entire directory is captured. Because aCOPYcan only read files inside the context, everything your Dockerfile needs travels with the backup. This is the reliable choice for any build that copies files. - Instance-directory context (
context: .): the Dockerfile is captured, but files itCOPYs from elsewhere in the instance directory are not (unless they already live under a backed-up path likeconfig/). Safe forRUN-only builds; forCOPYbuilds, use a subdirectory context instead.
Keeping your build files under version control is still good practice, but with a subdirectory context a backup alone is enough to reconstruct the instance. See Help:Backup and restore.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Your change isn't in the running container | canasta restart reused the cached image — restart never rebuilds |
Run docker compose build web (add --no-cache), then canasta restart
|
| Container runs the stock image despite the override | image: reuses the base image's name:tag, so Compose ran the already-present stock image |
Give image: a unique tag such as <id>:custom, rebuild, restart
|
| Customization vanished after an operation that pulled images | A docker compose pull overwrote a tag you shared with the registry image |
Use a unique local tag that the registry never publishes; rebuild |
php -m doesn't list the extension |
apt package name wrong, or PECL build failed silently in the layer | grep mongodb); for PECL builds confirm phpenmod ran
|
Extension loaded but feature absent on Special:Version |
PHP extension present, but the MediaWiki extension isn't enabled | Add the wfLoadExtension line — bundled extensions still need enabling (Help:Extensions and skins)
|
FROM built on the wrong Canasta version |
CANASTA_IMAGE not forwarded as a build arg, or unset in .env |
Confirm the args.CANASTA_IMAGE: ${CANASTA_IMAGE} line and that .env defines CANASTA_IMAGE
|
For the general problem catalog, see Help:Troubleshooting.