Unofficial, automated OCI images of FreeBSD, NetBSD, OpenBSD and DragonFly BSD, built from each project's own published userland, plus the measured routes from an ordinary host to a running one.
⚠ Proof of concept. One architecture, amd64. The shape is settled and the
coverage is not. docs/LIMITS.md is the honest account of
what works today, with the cost of each route in seconds.
Licence: 0BSD.
A BSD image needs a BSD kernel. It cannot run on a Linux container engine, and that is not a bug anybody can fix here.
podman run --rm ghcr.io/freebsd/freebsd-runtime:15.1 /bin/sh -c 'uname -a'Exit code 139, which is 128 + 11, a SIGSEGV. No stdout.
⭐ Read the number, not the text. 139 is not Exec format error. The
loader accepts the binary and it dies on its first syscall, because the
syscall ABI is a different operating system's. ⛔ So binfmt_misc and
qemu-user do not help: they translate a foreign architecture presenting
Linux syscalls.
⭐ Building these images needs no BSD kernel. Only running one does. That is why ordinary Linux CI can build them, and why nothing here smoke-tests them: no runner can run one.
The routes, and what each costs. ⛔ The numbers live in
docs/LIMITS.md and nowhere else; this table is a pointer.
| you have | you get | privilege |
|---|---|---|
| ⭐ only podman or docker | a NetBSD shell in a couple of seconds, under emulation, with the emulator inside the image | ⭐ none |
| an emulator, on Windows | a full FreeBSD 15.1 userland on the machine's own hypervisor | ⭐ none |
/dev/kvm |
a FreeBSD microvm in under two seconds | write access to /dev/kvm |
| a BSD host | ⭐ podman run on these images directly |
none |
⭐ The first row is one command, and this repository publishes the image:
podman run --rm -it ghcr.io/pkgforge-dev/netbsd:latest sh⛔ Nothing is installed and nothing is fetched at run time. The emulator,
the guest kernel and the guest root filesystem are in the image, so it works
with --network none and needs no privilege, no capability and no device.
⚠ -it is not optional: what you are given is a console.
Two variants, and only one of them is published today:
| tag | what is in it | state |
|---|---|---|
⭐ netbsd:latest |
a rescue userland. A shell and sysctl. ⛔ No uname, no ls, no compiler |
⭐ published. The command above pulls it |
netbsd:build |
⭐ a real userland: uname, make, pkg_add, pkgin, and a C and C++ compiler |
⛔ built and proved in CI, not yet published. IMG-02 |
⛔ Do not write a docker pull for netbsd:build yet. It does not exist in
the registry, and a command in a README that returns a 404 is worse than no
command.
⛔ What neither of them does yet: -v, -p and -e reach the container
and stop there. The BSD is a virtual machine inside it with its own filesystem,
so a source tree on your host is not visible to it and nothing you build inside
comes out. That is filed as IMG-03 in TODO/INDEX.md.
⛔ Three different acquisition methods. scripts/sources
is the single place that records which and why; everything else reads it.
| BSD | method | why |
|---|---|---|
| FreeBSD | oci |
⭐ Upstream publishes real OCI layout archives with a CHECKSUM.SHA256. They are verified and loaded, never rebuilt. Rebuilding an image somebody already publishes correctly is the most expensive mistake available. |
| NetBSD | sets |
base.tar.xz is already a root filesystem tar owned root/wheel, so it imports directly. |
| OpenBSD | sets |
base79.tgz is the same shape, owned root/bin. 535 MB. |
| DragonFly | iso |
⛔ Upstream publishes no set tarballs at all, only .img and .iso. The .img root filesystem is HAMMER2, which Linux cannot mount, so the disk image is a dead end on any CI runner. The ISO is ISO9660, confirmed by reading the CD001 signature at offset 32769, so bsdtar or 7z reads it anywhere. |
⚠ Only FreeBSD publishes OCI images upstream. For the other three these are, as far as this repository's authors could determine on 2026-08-27, the only published OCI images that exist.
⛔ And for three of the four, nothing can run what is published. That is
BSD-02, answered per BSD in HISTORY/references/usable.md.
sh scripts/build-bsd --bsd netbsdThat is a dry run: it fetches, verifies, imports, checks what the image declares, tags it locally, and prints what it would have published. Publishing is not something to do by accident, so it takes a flag:
sh scripts/build-bsd --bsd netbsd --pushFreeBSD has five variants, smallest first: static (559 KB), dynamic,
runtime, notoolchain, toolchain (215 MB).
sh scripts/build-bsd --bsd freebsd --variant staticWhat it does, in order. Fetch from the URL scripts/sources
declares. ⛔ For FreeBSD, verify the SHA-256 against the published checksum
file, with no flag to skip it. Import with the correct OCI os value.
⛔ Read back what the image actually declares and refuse it if the os or
arch is not what was asked for. Tag, and push only with --push.
⚠ The checksum proves integrity, not provenance. The checksum file comes from the same host as the artifact. What it catches is every accident, which is nearly everything that actually goes wrong. Signature verification is the next step up and is not implemented.
| tool | for |
|---|---|
podman or docker |
everything |
curl |
everything |
sha256sum or shasum |
FreeBSD's digest check. ⛔ Absence is an error, not a skip. |
bsdtar or 7z |
DragonFly only, to read ISO9660 |
⚠ On Windows, set MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' before
driving podman from Git Bash. The scripts are written to survive it: every
native binary is run from the working directory with a bare filename, never
given a path. That was measured here three times, once per tool.
docs/conventions/shell.md section 7.
⛔ Listed because a limit hidden is a defect filed against the user later. The
full account, with numbers, is docs/LIMITS.md.
| limit | what it means |
|---|---|
| ⛔ the images cannot run on Linux | see the top of this file. A BSD kernel is required |
| ⛔ no published image boots itself yet | the route is measured, the packaging is not built |
| ⚠ three of four BSDs have no runtime | ocijail exists for FreeBSD. Nothing equivalent exists for the others |
| ⚠ NetBSD and OpenBSD import one set | podman import takes a single tar. base is a complete userland; etc and the rest are not merged in |
| ⚠ DragonFly is method-verified, not yet built | the ISO9660 route was confirmed by reading the CD001 signature; the 748 MB extraction has not been run end to end |
| ⚠ no signature verification | integrity only, per above |
⚠ amd64 only |
one architecture is enough to prove the shape |
| ⚠ one release per BSD | pinned in scripts/sources. Publishing every release is a different decision with a retention policy attached |
sh tests/run.shsh scripts/common/check-gate.sh --fast⛔ Neither runs a BSD image, for the reason at the top of this file. The first checks the build scripts and the matrix; the second checks the repository. ⚠ A skipped check is reported as skipped and never counted as a pass.
| path | what |
|---|---|
scripts/sources |
⭐ the matrix, as data. Bumping a release is one edit here |
scripts/build-bsd |
fetch, verify, import, check, tag, push |
scripts/common/ |
the gate this repository runs on itself |
examples/ |
⭐ runnable. Start at 01-bsd-shell-with-only-podman.sh |
experiments/ |
⭐ every route to a running BSD, committed with its result, including the ones that failed |
docs/AGENTS.md |
⭐ the single entry point for an agent working here |
docs/HUMANS.md |
what a person runs, and the permissions block |
docs/LIMITS.md |
⭐ what does not work yet, and what each route costs |
TODO/ |
the work order, the entry list, and the last session's brief |
HISTORY/ |
⭐ every measurement and every claim this repository has withdrawn |