Repository navigation
Containers and Services
A rule can run its command inside a local container image through rootless Podman, and a shared service can hold a large index in memory once for many container rules. This page covers both, with a STAR alignment example that loads its genome once. You need rootless Podman and the images already pulled.
[[rules]]
name = "align_s1"
input = ["ref/star", "reads/s1.fq"]
output = ["aligned/s1.Aligned.out.sam"]
container = "quay.io/biocontainers/star:2.7.11b--h5ca1c30_8"
shell = """STAR --genomeDir ref/star --runThreadN $WORKZ_THREADS \
--readFilesIn reads/s1.fq --outFileNamePrefix aligned/s1."""
[rules.resources]
threads = 2
memory = "2G"container = "IMAGE" runs the rule's command with /bin/sh inside the image, with set -eu and pipefail where the shell supports it. Everything else about the rule stays the same: its inputs and outputs, its request, its place in the graph and its records.
Important
kanit never pulls images. Pull them with podman pull before the run; a missing image refuses the launch with container image IMAGE is not available locally; kanit does not pull images.
At the start of a launch, kanit resolves every image reference to its image ID and runs that ID. The ID is part of the rule key: retagging the same image reruns nothing, and a tag that now names a different image reruns the rules that use it.
-
Files. The working directory and the directory of every input outside it are mounted read-only at their own paths; the directory of every output, and the rule's
workdir, read-write. An output directly in the working directory makes the whole working directory writable, except.workz/, which stays read-only so a command cannot change the records that decide reuse. The command starts in theworkdir, or in the working directory, so relative paths work as in a host rule. -
Isolation. No network, no capabilities, no new privileges, and a read-only root file system; Podman gives the container its own writable
/tmp,/var/tmpand/runin memory. -
Environment. The image's own environment, plus
TZ,LANG,LC_ALL, the names given with--pass-env, andWORKZ_RULE,WORKZ_ATTEMPT_DIR,WORKZ_THREADSandWORKZ_MEMORY_MIB. The host'sPATHandHOMEdo not enter. - Resources. With isolation, the container's processes run in the attempt's cgroup, so the memory limit, the out-of-memory kill and the attempt's counters cover the tool itself; without isolation nothing limits them. The Podman client and conmon, its monitor, run in a separate group of the run, and take about 20 MiB per container; admission reserves 32 MiB for them beside the rule's request, so the budget covers them too.
mounts places host files or directories read-only over paths inside the image, for example a patched module or a configuration file the image expects in a fixed place:
[[rules]]
name = "multiqc"
# ...
container = "quay.io/biocontainers/multiqc:1.35--pyhdfd78af_2"
mounts = { "patches/report.py" = "/usr/local/lib/python3.14/site-packages/multiqc/report.py" }Each source is also an input of the rule, so editing it runs the rule again (plan says inputs changed). Targets are absolute paths other than /, without .., : or ,.
- A timeout,
kanit control stopor an interrupt stops the container withpodman stop, which sends the command a signal and kills it after the grace period. - The container of a failed attempt is removed. If the engine itself was killed, the next launch that uses containers removes its leftovers before it starts;
kanit cleanremoves them without starting a launch. - Exit status 125 means Podman failed or the command exited 125; 126 and 127 mean the command could not run in the image, usually a missing executable. The failure reason quotes the last line of the attempt's standard error.
Each container start costs Podman about 0.1 to 0.2 s, and Podman serializes much of it, so thousands of very short container rules start more slowly than host rules.
Some tools keep their index in System V shared memory, so that many processes can use one loaded copy. STAR does this with --genomeLoad: one process loads the genome (LoadAndExit), and later ones attach to it (LoadAndKeep). A shared service runs the loading step once in a container whose IPC namespace container rules then join.
flowchart LR
S[shared service genome<br/>STAR LoadAndExit] -- shared memory --> A1[align_s1<br/>STAR LoadAndKeep]
S -- shared memory --> A2[align_s2<br/>STAR LoadAndKeep]
A service is a [shared.NAME] entry with container, command and memory. Because a service runs in /tmp, its command needs absolute paths, so the workflow is best written by a script that knows them:
make-workflow.sh
#!/usr/bin/env bash
# Writes workflow.toml: align two samples with STAR against one genome held in shared memory.
set -euo pipefail
star=quay.io/biocontainers/star:2.7.11b--h5ca1c30_8
genome=$PWD/ref/star
keep="STAR --genomeDir $genome --genomeLoad LoadAndKeep --runThreadN \$WORKZ_THREADS"
{
cat <<EOF
[workflow]
name = "star-shared"
[shared.genome]
path = "ref/star"
container = "$star"
command = "STAR --genomeDir $genome --genomeLoad LoadAndExit --outFileNamePrefix /tmp/"
memory = "1G"
timeout = "10m"
EOF
for s in s1 s2; do
cat <<EOF
[[rules]]
name = "align_$s"
input = ["ref/star", "reads/$s.fq"]
output = ["aligned/$s.Aligned.out.sam"]
container = "$star"
shell = "$keep --readFilesIn reads/$s.fq --outFileNamePrefix aligned/$s."
[rules.resources]
threads = 2
memory = "512M"
shared = ["genome"]
EOF
done
} > workflow.tomlbash make-workflow.sh
kanit run workflow.toml --max-threads 4 --max-memory 4Gkanit 0.9.0 star-shared launch 20261108T121727.146-6881d0a6
plan 2 rules: 1 step
budget 4 threads, 4.0 GiB, 4 jobs, isolated
records .workz/latest/ log launch.log page report.html
TOOL STEP RULES
star align 2
TIME RULES STATUS TOOL STEP TOOK MEMORY CPU NOTE
05:17:27 0/2 INFO genome - / 1.0 GiB starting the service
05:17:27 0/2 INFO genome 0.3 s ready
05:17:27 0/2 START star align_s1 - / 512 MiB - / 2
05:17:27 0/2 START star align_s2 - / 512 MiB - / 2
05:17:28 1/2 DONE star align_s2 0.5 s 265 MiB / 512 MiB 0.7 / 2
05:17:28 2/2 DONE star align_s1 0.5 s 265 MiB / 512 MiB 0.7 / 2
The step's tool, star, comes from the image's name.
Both alignments report Found genome in shared memory in their STAR logs. The genome here is a small test genome; with a human genome the service holds about 30 GB once, instead of once per alignment.
-
Start. Before the first rule that lists the service in
resources.sharedstarts, kanit runscommandin a container of the image with a shareable IPC namespace, its own cgroup limited tomemory(with isolation),/tmpas its working directory, and the working directory andpathmounted read-only. -
Ready. The service is ready when
commandexits 0. Its users then start and join its IPC namespace, so they see the shared memory it created. The container stays up, holding that memory, until kanit stops it. -
Memory. The service's
memory, and 32 MiB for its Podman client, count once against the budget while it runs. With isolation, the shared memory it holds is charged to its cgroup, not to its users; without isolation nothing limits it. A user's own request covers only what it uses besides the index. - Lifetime. The service stays up while a rule that has yet to run needs it, so later users do not load it again. It gives way when a waiting rule needs its memory and no user is running, and it stops after its last user.
-
Time limit and retries.
timeoutlimits how long the service may take to become ready, andretries(0 to 3) starts it again if it fails before it is ready. After that, every rule that needs it fails withshared input NAME could not be loaded:and the reason. -
Failure while in use. A service that ends after it was ready, while rules use it, stops those rules: they fail with
shared service NAME failed while the rule used it:and the reason, without a retry, and nothing they wrote is recorded as a result. The memory they read may be gone. -
Logs. The service's standard error is kept in
.workz/launches/<ID>/shared/<NAME>-<N>.stderr.log, one file per start.
- Only container rules can join a service, and a rule can join one service.
-
pathmay hold files that rules of the same workflow produce, such as the index itself. - The service's image, command, path and mounts are part of its users' rule keys, and its
pathand the sources of itsmountsare inputs of every rule that joins it: a rule that writes the index or a mounted file runs before them, and rebuilding or changing one reruns them. Listing them ininputas well changes nothing. - A rule that joins a service may not write inside its
path: the service would load before the file exists. -
mountson a service places host files inside its image, as on a rule.
kanit report --bottlenecks lists every shared input with its starts, the rules that used it, its load time, the time it was held, its declared memory and its peak, and suggests a memory for services:
shared inputs
NAME TYPE STARTS USERS LOAD HELD MEMORY PEAK SUGGEST
genome service 1 2 0.2 s 0.8 s 1.0 GiB 191 MiB 256 MiB declares 768 MiB more than the suggestion
A service holds mostly what it loaded, so its suggestion is the peak plus a twentieth, rounded up to a multiple of 64 MiB. A service loaded more than once, or one that failed, says so at the end of its line.
kanit user guide for release 0.9.0. kanit is licensed under Apache-2.0.
Start
Write workflows
Run and observe
Reference