Docker image backbone for chapkit.
Three runtimes — Python, R, and R+INLA — each published in two variants:
- base (no suffix) — Python/R +
uv(and for R,renv+pak), nochapkit. Use as aFROMbase for scaffolded chapkit services that pin their ownchapkitversion and install viauv sync. This is whatchapkit initgenerates from. -cli— the base image plus a pinnedchapkitfrom PyPI. Use for invoking thechapkitCLI directly (e.g.docker run -v $PWD:/work ... chapkit mlproject migrate .) without needing apyproject.tomlor auv syncstep.
Why two variants: a scaffolded project that pins a different chapkit
version than a chapkit-bundled base takes the "uninstall + reinstall"
path through uv sync, which produces a measurably heavier runtime
memory profile than projects that match the bundled version. Keeping the
canonical base image free of chapkit makes the install path uniform
regardless of which chapkit release the project pins.
Lives in its own repository so the slow multi-arch R + INLA builds do not
block every commit on chapkit.
| Image | Base | Arches | ~Size (amd64) |
|---|---|---|---|
ghcr.io/dhis2-chap/chapkit-py:latest |
ghcr.io/astral-sh/uv:0.11-python3.13-trixie-slim |
linux/amd64, linux/arm64 |
~420 MB |
ghcr.io/dhis2-chap/chapkit-py-cli:latest |
ghcr.io/astral-sh/uv:0.11-python3.13-trixie-slim |
linux/amd64, linux/arm64 |
~570 MB |
ghcr.io/dhis2-chap/chapkit-r:latest |
debian:trixie-slim |
linux/amd64, linux/arm64 |
~385 MB |
ghcr.io/dhis2-chap/chapkit-r-cli:latest |
debian:trixie-slim |
linux/amd64, linux/arm64 |
~415 MB |
ghcr.io/dhis2-chap/chapkit-r-tidyverse:latest |
chapkit-r:latest |
linux/amd64, linux/arm64 |
~1.77 GB |
ghcr.io/dhis2-chap/chapkit-r-tidyverse-cli:latest |
chapkit-r:latest |
linux/amd64, linux/arm64 |
~1.81 GB |
ghcr.io/dhis2-chap/chapkit-r-inla:latest |
chapkit-r-tidyverse:latest (+ INLA build stage) |
linux/amd64 (INLA x86_64) |
~2.02 GB |
ghcr.io/dhis2-chap/chapkit-r-inla-cli:latest |
chapkit-r-tidyverse:latest (+ INLA build stage) |
linux/amd64 (INLA x86_64) |
~2.05 GB |
The size delta between each base / -cli pair is just the chapkit wheel
and its Python deps (~30–50 MB); the heavy bits (R, INLA, the Python
toolchain, the tidyverse + ML stack) are identical.
Hierarchy: chapkit-r-tidyverse FROMs chapkit-r and adds the
tidyverse + tidyverts + forecasting + ML primitives bundle (tidyverse,
fable, tsibble, feasts, lubridate, distributional, forecast, urca,
tseries, zoo, xts, readxl, ranger, randomForest, xgboost, glmnet,
e1071, lme4, janitor, here, patchwork). chapkit-r-inla FROMs
chapkit-r-tidyverse and adds INLA + the spatial/EWARS R stack
(fmesher, INLA, dlnm, sf, spdep, sn, tsModel, jsonlite). Models that
don't need INLA should pull chapkit-r-tidyverse to skip the ~250 MB
INLA layer.
Tag conventions:
:latest/:main— tip ofmain(chapkit installed from PyPI for-cliimages):sha-<short>— per-commit:<semver>— on tag pushes (e.g.:0.19.0,:0.19):dev—-cliimages only; nightly rebuild withchapkitinstalled from themainbranch ofgithub.com/dhis2-chap/chapkitinstead of PyPI. Use this to test against unpublishedchapkitchanges.
For how to build and run a chapkit service on top of these images, see the main chapkit docs:
chapkitdocschapkit init— scaffold a new chapkit servicechapkit migrate— convert an MLproject into a chapkit service
Both scaffolders generate a Dockerfile that FROMs the appropriate image
published by this repo.
.github/workflows/docker.yml builds and publishes all six images (base
-cli, for each of py / r / r-inla) toghcr.io/dhis2-chap/...from a single matrix job. Triggers:
- push to
main— refresh:latest - tag
v*— publish semver tag schedule(weekly, Mon 04:17 UTC) — base-image security refreshworkflow_dispatch— manual withchapkit_versioninput (PyPI version, e.g.1.1.0; only affects-cliimages)repository_dispatch(event_type: chapkit-release) — fired by chapkit's release workflow (rebuilds-cliimages against the released version)
After all six builds succeed, a smoke job pulls each :main image and
runs chapkit --help against the -cli tags (plus library(INLA) for
chapkit-r-inla-cli) and uv --version against the base tags, to catch
broken publishes. On scheduled / workflow_dispatch runs, a parallel
build-dev job rebuilds the three -cli images against chapkit main
and tags them :dev; a corresponding smoke-dev job exercises those.
Build cache is pushed to a :buildcache tag alongside each image so PR and
main-branch builds share cached layers via the registry.
make build # all six images, default chapkit version
make build-py # base Python image only (no chapkit)
make build-py-cli # Python image with chapkit pre-installed
make build-r # base R, multi-arch
make build-r-cli # R with chapkit pre-installed
make build-r-inla # base R + INLA, amd64
make build-r-inla-cli # R + INLA with chapkit pre-installed
make build-py-cli CHAPKIT_VERSION=1.1.0 # pin a specific PyPI version
make build-r-inla INLA_VERSION=26.08.07 # build against a different INLA versionchapkit-r-inla pins INLA via ARG INLA_VERSION in
chapkit-r-inla.Dockerfile rather than
resolving to newest-in-channel, so a cache miss cannot silently change the
published image. Bump that arg to move the pin; the INLA_VERSION make
variable above is for trying a version out without editing the Dockerfile.
Images run as root by default and ship an unprivileged chapkit user
(uid/gid 1000). Services scaffolded by chapkit switch to it with USER chapkit
and apply the same compose hardening as
chap-core/compose.yml.
Intended to sit in a trusted compose network behind chap-core.