The engine in a container
The published image, a volume for the registry, and the one podman flag that matters.
The wizard does this for you. This page is what it does, for when you would rather do it yourself.
The engine is a binary and a directory, so the image is small and the recipe is short. Everything below was run with rootless podman; docker takes the same commands without the :U suffix.
The image
Every release publishes one, for x86-64 and arm64, and it needs no account to pull:
podman pull ghcr.io/kineuro/nils:v1.0.0-beta.1
The commands on the rest of this page call it $IMAGE, so set it once:
IMAGE=ghcr.io/kineuro/nils:v1.0.0-beta.1
The tag is the release tag, so it begins with a v. There is a latest as well, which follows the newest release including a prerelease, so name the version you mean.
The image holds a Debian slim base with the PostgreSQL client, which the backup of a registry kept in Postgres runs, the same nils binary the release page offers, and the rule packs. It holds no registry, no key and no configuration. Those live in the volume you mount, which is why the same image serves a laptop and a ward.
Building it yourself
Where the machine cannot reach the registry, or where you want a binary you built, the recipe is short:
FROM docker.io/library/debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1500 nils && useradd -u 1500 -g 1500 -M -d /srv/nils nils \
&& mkdir -p /srv/nils/registry && chown -R nils:nils /srv/nils
COPY nils /usr/local/bin/nils
COPY packs /srv/nils/packs
USER nils
WORKDIR /srv/nils
EXPOSE 8437
ENTRYPOINT ["nils"]
CMD ["serve", "--bind", "0.0.0.0:8437", "--registry", "/srv/nils/registry", "--pack-dir", "/srv/nils/packs"]
Put the nils binary you built and the packs/ directory beside that file, then:
podman build -t nils:local .
IMAGE=nils:local
The rest of the page then runs your image instead of the published one.
To build the binary inside the container instead, put
FROM docker.io/library/rust:1.98-bookworm AS buildin front, copy the source in,cargo build --release -p nils, and copy the binary from that stage. It is slower and needs nothing on the host but podman.
The registry lives outside the image
Make it once, in the volume, with the same two commands as on the machine:
mkdir -p ~/nils/registry
podman run --rm -i -v ~/nils/registry:/srv/nils/registry:U "$IMAGE" \
--registry /srv/nils/registry key add nils # the passphrase on stdin
podman run --rm -v ~/nils/registry:/srv/nils/registry:U "$IMAGE" \
--registry /srv/nils/registry init --key nils
:U tells rootless podman to give the container’s user ownership of the bind mount. Without it the container cannot write, and the error is a permission denied that looks like a bug.
Run it
podman run -d --name nils -p 127.0.0.1:8437:8437 \
-v ~/nils/registry:/srv/nils/registry:U \
"$IMAGE"
curl -s http://127.0.0.1:8437/api/capabilities | head
podman logs -f nils
Reading your DICOM files
Mount them read only and name the mount as an ingest root, so the engine only ever walks a place you registered:
podman run -d --name nils -p 127.0.0.1:8437:8437 \
-v ~/nils/registry:/srv/nils/registry:U \
-v /data/source:/data/source:ro \
"$IMAGE" serve --bind 0.0.0.0:8437 \
--registry /srv/nils/registry --pack-dir /srv/nils/packs \
--ingest-root source=/data/source --worker
The wizard mounts the registry, the backups and every source place at the paths they have on the machine, so the places the registry records are paths the engine sees. When you declare places yourself, name them by the paths the engine sees inside its container.
As a service
podman generate systemd --new --name nils > ~/.config/systemd/user/nils-engine.service
systemctl --user daemon-reload && systemctl --user enable --now nils-engine
On podman 5, podman generate systemd is deprecated in favour of Quadlet: put a nils.container file in ~/.config/containers/systemd/ and systemd builds the unit for you.
Which to choose
A container pins the libraries and keeps the engine away from the rest of the machine. Running it directly is one process, one fewer layer to debug, and the same speed. We run it directly on our own machine and in a container elsewhere; both are supported.
Next: backups.
Updated 17 October 2026