Help:User journeys/Extending Canasta images with custom Docker builds

From Canasta Wiki

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 docker and canasta.
  • The instance directory (the folder created by canasta create, containing .env and docker-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-mongodb PHP 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:

  1. Always give your custom build a unique image tag — e.g. <instance-id>:custom. Never reuse the base image's name:tag (ghcr.io/canastawiki/canasta:latest). A Compose service that has both image: and build: 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 later docker compose pull can overwrite your build from the registry. A unique tag is written only by your build, so nothing else clobbers or shadows it.
  2. Rebuild before you restart. canasta restart runs docker compose up -d, which never rebuilds an image that already exists — it reuses the cached tag. After any change to your Dockerfile you must run docker compose build <service> (add --no-cache when in doubt) before canasta 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's FROM, 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 restartingcanasta 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:

  1. Remove the web: block (or just the image:/build: keys) from docker-compose.override.yml. If the override now has no remaining content, delete the file.
  2. Disable the MediaWiki extension you added (remove its wfLoadExtension line).
  3. Recreate the container on the stock image:
    canasta restart --id <your-instance-id>
    
  4. Optionally remove the now-unused custom image to reclaim disk:
    docker image rm <your-instance-id>:custom
    
  5. Delete Dockerfile.custom if 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.custom and the override are the only record of how your image differs from stock; treat them like code. canasta backup captures them too (see below), but version control is still where they belong.
  • Pin or review the version your FROM inherits. Because CANASTA_IMAGE drives 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 RUN layer (as the PECL example does).
  • The same pattern applies to other services. To add Elasticsearch plugins, override the elasticsearch service 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 a COPY can 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 it COPYs from elsewhere in the instance directory are not (unless they already live under a backed-up path like config/). Safe for RUN-only builds; for COPY builds, 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.