Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The chapkit CLI

chapkit is the framework of the model services. Its command line, chapkit, creates a model project, changes an MLproject model to a chapkit service and back, and serves an MLproject model with no image to build. It is usually installed with uv, which needs Python on the machine.

chap-devops chapkit runs the same chapkit in a container. You need Docker and nothing more:

chap-devops chapkit mlproject migrate my-model
chap-devops chapkit mlproject run my-model

Each argument after chapkit goes to chapkit unchanged. To read chapkit’s own help, run chap-devops chapkit with no arguments. chap-devops chapkit --help shows only the options of chap-devops itself.

Which image

chapkit is a dependency of chap-core, so the chap-core image has the chapkit CLI. chap-devops runs it in that image, and there is no other image to pin or to update. The chap-core image v2.4.0 has chapkit 2.1.0. From v2.5.0 it has chapkit 2.4.0, which adds chapkit mlproject export and writes the full MLproject contract with migrate; v2.5.1 has chapkit 2.4.1. In a deployment on an older tag, add --tag v2.5.1 for these.

The version follows the rules of chap-devops chap: in a deployment, the deployment’s chap-core tag; outside a deployment, the newest chap-core release; --tag TAG overrides both. See Which version. The image is amd64 only, so on an arm64 machine, such as a Mac with Apple silicon, it runs under emulation.

chapkit mlproject run is the exception: it runs in a chapkit -cli image. See Serve an MLproject model.

Your files

chap-devops mounts the current directory in the container at the same path, and the container runs as your user. So the paths in the arguments mean the same in the container, and the files that chapkit writes are yours. A path outside the current directory is mounted too. See Your files.

Migrate an MLproject model

chapkit mlproject migrate DIR makes a chapkit service from the MLproject file in DIR. It moves the files that the service does not need to _old/, and writes main.py, pyproject.toml, Dockerfile and the other files of a chapkit project. Without --yes, it asks first; in a terminal, you answer the question as with a local chapkit.

chap-devops chapkit mlproject migrate my-model --yes

The output ends with the lines of chap-devops:

chapkit finished; it wrote my-model/.dockerignore, my-model/.gitignore, my-model/CHAPKIT.md, my-model/Dockerfile, my-model/Makefile, my-model/README.md, my-model/_old/.gitignore, my-model/_old/MLproject, and 7 more
wrote my-model/uv.lock with `uv lock` in the same image, so `docker build` works

The Dockerfile of a chapkit project copies uv.lock, and chapkit tells you to run uv lock on your machine. chap-devops does this for you: when the run writes a pyproject.toml and the Dockerfile next to it copies a uv.lock that is not there, chap-devops runs uv lock in the same image.

Then build the image and test it, as for a new model project:

cd my-model
docker build --platform linux/amd64 --build-arg GIT_REVISION=$(git rev-parse HEAD) -t my-model:dev .
chap-devops run my-model:dev
chap-devops -C ~/.local/share/chap-devops/run/default models test my_model

chapkit selects the base image of the service from the scripts: the ones at the root of DIR, and the ones that the train and predict commands name, such as scripts/train.py in the mlproject-* templates. R scripts give an R image, Python scripts give chapkit-py. If it finds none, chapkit stops with No Python (.py) or R (.r/.R) scripts at the project root or in the entry-point commands. Then give the image:

chap-devops chapkit mlproject migrate my-model --yes --base-image chapkit-py

The values are chapkit-py, chapkit-r, chapkit-r-tidyverse and chapkit-r-inla. chap-core v2.5.0 and older (chapkit 2.4.0) look at the root only, so there the mlproject-* templates need --base-image.

On chap-core master before v2.5.0 (chapkit 2.4.0), these models were migrated, built, and passed the model-level test with 1 training and 1 prediction: minimalist_configurable_model and mean from chap-models, and the mlproject-py and mlproject-r templates with --base-image. On v2.5.0, the mlproject-py template passed again. On v2.5.1, the mlproject-r template was migrated with no --base-image, built, and passed.

Export a service as an MLproject

chapkit mlproject export reads the main.py of a chapkit service, and writes the MLproject file that describes the same model. It needs chapkit 2.4.0, so it needs chap-core v2.5.0 or later:

cd my-service
chap-devops chapkit mlproject export main.py -o MLproject

chapkit imports main.py to read the service. This works in the chap-core image when main.py imports only chapkit and the Python standard library, as a service that runs scripts does. If main.py imports the packages of the model, the import can fail in the chap-core image.

Only a service that runs scripts can be exported, such as one from the shell-py or shell-r template. For a model in main.py (the fn-py template), chapkit stops with Only ShellModelRunner services can be exported.

The exported MLproject gives the train and predict commands a fixed config.yml, which chapkit writes. So it runs where chapkit runs it: with chap-devops chapkit mlproject run, and with chap-devops chap --run-config.as-chapkit. Without --run-config.as-chapkit, chap does not write config.yml, and the train command stops with No such file or directory: 'config.yml'.

Serve an MLproject model

chapkit mlproject run DIR serves an MLproject model as a chapkit service, with no image to build:

chap-devops chapkit mlproject run my-model
chap-devops chapkit --image r-inla mlproject run my-model
chap-devops chapkit --image chapkit_ghr_model:dev mlproject run .

The model needs its language and chapkit in one image. The chap-core image has no R, so chap-devops runs a chapkit -cli image instead:

--imageImage
pyghcr.io/dhis2-chap/chapkit-py-cli
py-dataghcr.io/dhis2-chap/chapkit-py-data-cli, with numpy, pandas, scipy, scikit-learn, statsmodels, joblib, pyyaml and xarray
rghcr.io/dhis2-chap/chapkit-r-cli
r-tidyverseghcr.io/dhis2-chap/chapkit-r-tidyverse-cli
r-inlaghcr.io/dhis2-chap/chapkit-r-inla-cli (amd64 only)
any other valueThat image, such as one you built yourself

chap-devops chapkit --image, with no value, lists the short names, what each image has, and whether this docker has it already.

Without --image, the train command in MLproject selects the image: a python or uv command gets py-data, and an Rscript command gets r-tidyverse. A model that needs INLA, or packages that no -cli image has, needs --image. An image of your own must have chapkit 2.4.0 or later, such as an image built FROM a -cli image. chap-devops pulls the image if this docker does not have it.

chap-devops mounts the model directory at /work and publishes the service on the first free port from 5001 (in a deployment, from its port range). It prints the URL when the service answers at /health. The logs follow in the terminal, and Ctrl-C stops the service and removes its container. A kill of the chap-devops process does the same, and so does docker stop of the container.

The image has chapkit and the packages in the table above. chap-devops does not install the dependencies that the MLproject names in uv_env or renv_env. If the model needs a package that the image does not have, the train job stops with an error such as No module named 'cyclopts'. Then give --image with an image that has the package, or let chap install them with chap-devops chap --run-config.as-chapkit (The chap CLI).

If the model directory is a git checkout, chap-devops gives chapkit its commit as GIT_REVISION, as the docker build of an image does. chap-core stores a model only with a revision. If the directory has changes that are not committed, chap-devops warns: the service reports the last commit, so commit the changes and raise version in MLproject first.

The options of chapkit mlproject run after DIR go to chapkit unchanged, such as --config-format flat or --log-level debug. A relative --database path is in the model directory. chap-devops sets --host and --port itself.

In a deployment that runs chap-core, the service joins the deployment network and registers with chap-core while it runs, with the registration key from .env. Then the commands of the deployment use it by its id, the name in MLproject:

chap-devops models configs sync my-model
chap-devops models test my-model --backtest

chap-devops status lists the service as unmanaged, with the address where chap-core reaches it, because chap-devops does not manage it.

Outside a deployment, or with an external chap-core, it does not register; use the URL that chap-devops prints. chap-devops chap runs in a container, where localhost is the container, so give it the same port at host.docker.internal:

chap-devops chap eval --model-name http://host.docker.internal:5001 \
  --dataset-csv laos_subset.csv --output-file my-model.nc

What chap-devops refuses

chapkit test needs a model service that the container can reach. chap-devops refuses it and names the command that does the work:

CommandUse instead
chapkit testchap-devops models test ID, which runs chapkit test in the model’s own container.

If uv lock fails, chap-devops gives a warning; see could not write DIR/uv.lock.

For a new project, chap-devops models new DIR is the same as chapkit init, with the templates in chap-devops. See A new model project. chap-devops chapkit init NAME also works, and gets a uv.lock the same way.

--json does not apply: the output is chapkit’s own.