Run the Toolchain in a Container¶
An alternative to installing the toolchain on your machine. The
image carries uv, just, Python 3.12 and the dev dependency group, so every just recipe
runs without Python, uv or just being present on the host.
This is a development environment, not a shipping artifact. The package is published as a
wheel built from pyproject.toml; nothing in the image takes part in that build.
Requirements¶
- Docker with the Compose plugin (
docker compose version) - The repository cloned locally — the image supplies the environment, your working tree supplies the code
No Docker license? Use Podman
Everything on this page works unchanged with Podman instead of
Docker — useful where Docker Desktop's license doesn't apply (e.g. larger companies
without a subscription). Verified end to end on Windows: winget install RedHat.Podman,
then pip install podman-compose to give podman compose a Compose provider (Podman
itself doesn't ship one). podman machine init --now creates the Linux VM Podman needs
on Windows — it uses WSL2 the same way Docker Desktop does, so the same WSL2 prerequisite
applies. From there, every command on this page is podman compose in place of
docker compose, unchanged otherwise.
On Windows specifically, podman compose run --rm ontology-tools id reports
uid=1000(omb) regardless of HOST_UID/HOST_GID — but a file that recipe creates
still lands on the NTFS side owned by your real Windows account, not root, because
Windows enforces access through NTFS permissions on the account running Podman rather
than the container's Linux-side uid (see the Windows note under Build).
WSL2 without Docker Desktop
A third option, free of both Docker Desktop's license and Podman's extra
podman-compose dependency: install Docker Engine directly inside your WSL2 distro
(sudo apt install docker.io docker-compose-v2) instead of Docker Desktop. This is
plain Docker CE — Docker Desktop is the piece that needs a license, not the engine —
and everything on this page works unchanged from a WSL bash shell against it.
Right after installing, docker.service can fail to start under systemctl on some
WSL2 setups (Job for docker.service canceled) even though dockerd itself is fine —
observed on a fresh install here. wsl --shutdown from PowerShell, then reopening the
distro, resolves it in most cases by giving systemd a clean restart to pick up the
newly installed units and the docker group. If it still doesn't start, run
sudo dockerd in a spare terminal as a fallback — everything on this page works
against it identically either way.
Build¶
The container runs as a non-root user so that files a recipe creates (docs-build's
site/, a new generate-domain directory, coverage's htmlcov/, ...) come back owned by
you, not root, on the bind-mounted host tree. It defaults to uid/gid 1000, the common
single-account Linux default; build with your own to match exactly:
If you skip this and your account's ids differ from 1000, files the container writes are
still yours to read, but not to overwrite or delete without sudo — rebuilding with the
right ids, or running docker compose run --rm ontology-tools chown -R $(id -u):$(id -g) /workspace
after the fact, both fix it.
Windows
HOST_UID/HOST_GID matching is a Linux/WSL2 concept — NTFS has no uid or gid to
match in the first place. Whether it applies depends on where the repository is
cloned, not on Windows itself:
- Cloned inside the WSL2 filesystem (e.g.
\\wsl$\Ubuntu\home\you\..., notC:\...), built from a WSL bash shell — WSL2 is a real Linux VM with real uid/gid semantics, so this works exactly as described above; use the same command. - Cloned on the Windows filesystem (
C:\..., including as/mnt/c/...from WSL) — Docker Desktop bridges that bind mount over a protocol with no uid/gid of its own. Files the container writes typically show up owned by a fixed, synthetic identity no matter whatHOST_UID/HOST_GIDis set to, andchowninside the container does not persist. In practice this is usually not the blocking version of the problem described above, though: Windows enforces access through the NTFS permissions of the account running Docker Desktop, not the Linux-side uid, so Explorer/VS Code can typically still edit or delete those files regardless of what adocker compose run --rm ontology-tools ls -lareports.
$(id -u)/$(id -g) are bash syntax; from PowerShell, set the same build args
without them (the default matches the common single-account WSL2 case, uid/gid
1000, so this is only needed if your WSL2 account's ids differ from that):
Run¶
compose.yaml bind-mounts the repository at /workspace, so edits on the host are visible
immediately and generated artifacts land back in your working tree.
# Interactive shell with the toolchain
docker compose run --rm ontology-tools
# Any recipe, without entering a shell
docker compose run --rm ontology-tools just --list
docker compose run --rm ontology-tools just lint
docker compose run --rm ontology-tools just test
docker compose run --rm ontology-tools just test-domain hdmap
Validate a Single File¶
A file inside the repository needs no extra setup — pass its repo-relative path:
docker compose run --rm ontology-tools \
just validate-file tests/data/manifest/valid/manifest_instance.json
A file outside the repository is not visible to the container until you mount it. Add a second bind mount and refer to the file by its path inside the container:
docker compose run --rm -v /absolute/path/to/my-data:/data:ro ontology-tools \
just validate-file /data/my_manifest.json
:ro keeps the mount read-only, which is enough — validation only reads your data. Mount the
containing directory, not the file: validate-file scans the parent directory for
fixtures to resolve did:web: references from.
The exit code is the result, so this works in a script or CI step: 0 means the data
conforms and 210 is a conformance error, with the violations printed above it.
To validate several files, or a whole directory, use the underlying flag:
docker compose run --rm -v /absolute/path/to/my-data:/data:ro ontology-tools \
just validate --data-paths /data/
Things Worth Knowing¶
Files the container writes are yours, not root's — if you built with your own ids.
See Build above: without HOST_UID/HOST_GID set to match your account, a
recipe that creates something new (rather than overwriting a file already there) leaves it
owned by uid/gid 1000 instead of you. On Windows this only applies at all when the
repository is cloned inside the WSL2 filesystem — see the Windows note under Build.
The environment lives in a named volume, not in your working tree. UV_PROJECT_ENVIRONMENT
points at /opt/venv, backed by a Docker volume, so a host .venv cannot shadow it — and a
host .venv built for a different platform cannot break the container.
After a rebuild that changes dependencies — or HOST_UID/HOST_GID — reset that volume.
Docker seeds a named volume from the image only while the volume is still empty, so an
existing volume keeps shadowing the new image, environment and ownership alike:
Submodules are bind-mounted, not baked in. The image deliberately excludes
submodules/ — around 300 MB that would only slow the build down. Tests that read the pinned
standards work because the bind mount carries them, so initialise them on the host as usual:
just lint needs a real .git directory. pre-commit does. If you work in a
git worktree, its .git is a file pointing outside the bind mount, where pre-commit
cannot follow it — run lint from a normal clone, or on the host.
The image needs no network at run time. Dependencies and the project are installed at
build time, so recipes work offline. docker compose run --rm ontology-tools with networking
disabled is a supported way to prove data validates against nothing but the pinned artifacts.