As a BRO (BISOS Repo Object), this repo contains Dockerfile specifications for building BISOS Docker container images.
Images are organized along an init/isolation profile dimension — three peers describing how the container is initialized and how much privilege it holds:
- confined
- Unprivileged containers. No systemd. VNC/noVNC/sshd started
directly via
entrypoint.sh. Safe to run anywhere; cgroup-version-agnostic. - privileged
- Full
--privilegedcontainers (Docker) with systemd as PID 1. VNC/noVNC/sshd managed as systemd units. Socket activation works. Behaves as close to a VM as possible. Intended for trusted dev environments. - rootless-sysd
- Same systemd-as-PID-1 image, but run under rootless
Podman — systemd fidelity with the unprivileged/userspace security
posture (no
--privileged). Requires a cgroup v2 host. Intended for multi-tenant use (N engineers on one shared host).
The three peers are named by property, not by tool. See Host Compatibility for which profile works on which host.
Each variant targets Debian 12 (Bookworm) and Debian 13 (Trixie). All images provide an XFCE4 desktop accessible via TigerVNC and noVNC.
Joint design with =bisos.dockerProc= .
This repo and the bisos.dockerProc pip package are jointly designed and
meant to be used together. bisos.dockerProc provides the framework
(planted .spcs files, path-derived parameters, seed CS commands);
bro_dockerfiles provides the image specifications (Dockerfiles, systemd
units, per-leaf assets) that the framework operates on. The two repos
evolve together. Today the bash scripts (build.bash, verify.sh,
run.bash) still work as a standalone alternative; a future release will
make .spcs the only path. The
Repo Structure at a Glance figure below
shows both parts of the pair.
Architectural context. bro_dockerfiles is one realization of a
general BISOS pattern: a walkable BPO tree whose leaves are planted
.spcs files driven by a domain-specific seed. In the three-box
architecture documented at
=bisos.pycs/spcs/README.org=,
this repo is Box 3 (Planted Tree) — the on-disk artifact the
engineer assembles by planting .spcs files and marker files, not by
writing Python. bisos.dockerProc is the corresponding Box 2 (Custom
Seed) — the domain seed authored once. Box 1 (PyCS Infrastructure)
— bisos.csSeed, bisos.fileObj — is consumed as-is by both.
Future BPO trees under the same pattern (bro_lcnt, static-web
publication, and others) will follow the same shape with different
leaf payloads and different domain seeds.
Within this tree today, debian/12/confined/ carries branch-side
ftoBranchProc.spcs and marker files so it can be walked via
fto_forwardToLeaves across its descendants; the other five leaves
(privileged, rootless-sysd, deb13 chain) will follow. See
=bisos.fileObj/README.org=
for the walker; see the pycs/spcs/README.org link above for the
full architecture.
The figure below is a compact summary of everything in this repo: six
container image leaves arranged as a 2 x 3 grid, their FROM inheritance,
and the two build/verify paths available for each leaf. Scan the legend
first, then the figure.
Legend:
| Element | Meaning |
|---|---|
| Thistle cylinder (top) | External Debian base image pulled from Docker Hub. |
Light-blue leaf (box3d) | confined profile leaf — unprivileged docker, no systemd. |
Pink leaf (box3d) | privileged profile leaf — docker with --privileged + systemd PID 1. |
Khaki leaf (box3d) | rootless-sysd profile leaf — rootless podman + systemd PID 1. |
FROM arrow | Image inheritance. Confined images inherit from Debian; sysd variants inherit from confined. |
(same FROM) dotted connector | Privileged and rootless-sysd of the same release share their FROM base. |
| Yellow rectangle (dashed border) | Bash path (legacy) — build.bash / verify.sh / docker compose / run.bash. Retires later. |
| Green rectangle | =.spcs= path (current) — dockerProc.spcs / podmanProc.spcs via bisos.dockerProc. |
| Thistle ellipse (right) | bisos.dockerProc — external pip package that provides the .spcs files. |
| Dotted “plants” arrow | bisos.dockerProc plants .spcs files into every leaf. |
| Gray dashed “operates on” arrow | Each path operates on any leaf (single edge drawn for legibility). |
Reading the figure, top to bottom:
- External Debian base images (top).
debian:12.12anddebian:13.1are pulled from Docker Hub. Each feeds one confined leaf. - 2 x 3 grid of leaves. Rows are Debian release (12, 13); columns are
init/isolation profile (confined, privileged, rootless-sysd). Within
each row, the privileged and rootless-sysd leaves both
FROMthe same confined image — so building the confined image first is the prerequisite for the two sysd variants. - Two build/verify paths (bottom-left). Every leaf supports two ways
of being built, verified, and run. The bash path (
build.bash,verify.sh,docker compose,run.bash) works standalone — nobisos.dockerProcinstall required, keeps the repo usable on its own. The.spcspath (dockerProc.spcsfor docker leaves,podmanProc.spcsfor rootless-sysd leaves) derives all parameters from the leaf’s directory path viabisos.dockerProc. The bash path is dashed because it will be retired in a future release once the.spcspath has full parity. - =bisos.dockerProc= (bottom-right). The companion pip package
— the framework side of this pair.
bro_dockerfilesprovides the image specifications;bisos.dockerProcprovides the machinery that builds, runs, and verifies them. It plantsdockerProc.spcsinto confined + privileged leaves andpodmanProc.spcsinto rootless-sysd leaves. Once planted, each.spcsfile is invoked directly from its leaf directory. Sources: https://github.com/bisos-pip/dockerProc.
- Repo Structure at a Glance
- Scope of This Repo
- Structure of This Repo
- Images
- Host Compatibility (cgroup v1 vs v2)
- Host OS / Podman Version for rootless-sysd (Debian 12 vs 13)
- Feature Parity: privileged vs rootless-sysd
- Building
- Running
- Connecting to a Container
- Installing Raw-BISOS Inside a Container
- Related Resources
Target Debian releases:
All images provide:
- XFCE4 desktop environment
- TigerVNC server (port 5901 inside container)
- noVNC web client (port 6901 inside container)
- OpenSSH server (port 22 inside container)
- Base applications: Firefox ESR, Emacs, git, gedit, meld, synaptic
- Default user
bystar/ passwordinsecurewith passwordless sudo
BISOS’s equivalent of the common CI/CD pipeline is called
RawBisos-Reconstruction. The six leaves in this repo (deb12/deb13 ×
confined/privileged/rootless-sysd) are the concrete test matrix for the
container-based variant. Each image bakes ~/raw-bisos/installRawBisos.sh
which refreshes raw-bisos.sh from bxGenesis/start and runs it
non-interactively.
The full lifecycle (build → run → bootstrap from bxGenesis → verify → tear
down) runs from planted .spcs files without a CI service or VM:
./ftoBranchProc.spcs -i fto_forwardToLeaves --cmndName=containerProc_exec_installRawBisos
Complements the Vagrant-based variant in bxGenesis/start (heavier;
full-release validation). See bisos.dockerProc/README.org for the
pipeline verbs (containerProc_imageBuild, containerProc_instanceUp,
containerProc_exec_installRawBisos, containerProc_instanceVerify,
containerProc_fullClean) and the full one-shot recipe.
debian/
<majorRelease>/
confined/vnc/xfce/<imageName>/
privileged/vnc/xfce/<imageName>/
rootless-sysd/vnc/xfce/<imageName>/
| Path | Base image | Init | Engine / posture |
|---|---|---|---|
debian/12/confined/vnc/xfce/bisos_deb12-fresh/ | debian:12.12 | entrypoint.sh | docker, unprivileged |
debian/12/privileged/vnc/xfce/bisos_deb12-sysd/ | bisos/deb12-fresh-vnc-xfce | systemd | docker, --privileged |
debian/12/rootless-sysd/vnc/xfce/bisos_deb12-rootless-sysd/ | bisos/deb12-fresh-vnc-xfce | systemd | podman, rootless |
debian/13/confined/vnc/xfce/bisos_deb13-fresh/ | debian:13.1 | entrypoint.sh | docker, unprivileged |
debian/13/privileged/vnc/xfce/bisos_deb13-sysd/ | bisos/deb13-fresh-vnc-xfce | systemd | docker, --privileged |
debian/13/rootless-sysd/vnc/xfce/bisos_deb13-rootless-sysd/ | bisos/deb13-fresh-vnc-xfce | systemd | podman, rootless |
Both the privileged and rootless-sysd images use the confined image as their
FROM base, adding only the systemd layer on top. Build the confined image
first. The privileged and rootless-sysd images share an identical build; they
differ only at run time (--privileged + cgroup mounts vs. rootless Podman’s
native systemd integration).
Each image directory contains:
| File/Dir | Purpose |
|---|---|
| File/Dir | Purpose |
Dockerfile | Image definition |
docker-compose.yml | Convenience compose file for local use |
build.bash | Build and push multi-arch images to DockerHub |
dockerProc.spcs | (docker leaves) Planted Spread CS from bisos.dockerProc; drives build/run/verify from this dir |
podmanProc.spcs | (rootless-sysd) Planted Spread CS; podman equivalent of dockerProc.spcs |
dockerConf/entrypoint.sh | (confined only) Starts VNC, noVNC, sshd directly |
dockerConf/xstartup | (confined only) VNC xstartup launching XFCE4 |
dockerConf/panel.sh | (confined only) XFCE4 panel launcher config (NOTYET) |
dockerConf/panel.desktop | (confined only) Autostart entry for panel.sh |
dockerConf/terminalrc | (confined only) XFCE4 terminal unicode settings |
dockerConf/vncserver.service | (sysd) systemd unit for TigerVNC |
dockerConf/novnc.service | (sysd) systemd unit for noVNC |
dockerConf/sshd-container.service | (sysd) systemd unit for sshd |
dockerConf/tmp-exec.service | (privileged) oneshot: remount /tmp exec at boot |
docker-compose.cgv1.yml | (privileged only) cgroup-v1 host overlay |
run.bash | (rootless-sysd) podman run launcher |
*.container | (rootless-sysd) Podman Quadlet unit (per-engineer) |
verify.sh | (sysd) host-side smoke test of a running container |
raw-bisos (symlink) | (confined only) → ../../../../../../common/raw-bisos; docker COPY follows symlink to bake ~/raw-bisos into the image. Sysd variants inherit via FROM. |
No systemd. Services started by entrypoint.sh, container stays alive via
sleep infinity. Can run on any Docker host without special privileges.
The bystar user is created with home directory mode 755 (overriding the
useradd default of 700 from /etc/login.defs). The privileged and
rootless-sysd images inherit this via their FROM base.
| Image | Tag | Debian | DockerHub name |
|---|---|---|---|
bisos/deb12-fresh-vnc-xfce | 1.21 | 12.12 | bisos/deb12-fresh-vnc-xfce:1.21 |
bisos/deb13-fresh-vnc-xfce | 4 | 13.1 | bisos/deb13-fresh-vnc-xfce:4 |
systemd as PID 1. Full socket activation, timers, journald, systemctl all
work. Requires a Debian 12/13 host with cgroupv2 (cgroup2fs) and Docker
Engine. Intended for trusted dev environments.
The privileged images layer on top of the confined images — build confined first.
| Image | Tag | Base image |
|---|---|---|
bisos/deb12-sysd-vnc-xfce | 1 | bisos/deb12-fresh-vnc-xfce:1.21 |
bisos/deb13-sysd-vnc-xfce | 1 | bisos/deb13-fresh-vnc-xfce:4 |
On a cgroup-v1 host that cannot be moved to v2, use the docker-compose.cgv1.yml
overlay instead of docker-compose.yml (see Host Compatibility).
Docker tmpfs and =/tmp=. Docker’s tmpfs: shorthand mounts /tmp with noexec
by default — unlike Debian’s own /tmp which is exec. The privileged images
include a tmp-exec.service systemd oneshot that runs mount -o remount,exec /tmp
early at boot (after local-fs.target, before sysinit.target), restoring
Debian’s default behaviour. The confined images are not affected (their /tmp is
on the container’s overlayfs root, not a Docker tmpfs mount).
The same systemd image as privileged, but run under rootless Podman — no
--privileged, no host /sys/fs/cgroup bind mount. Podman’s native systemd
integration (--systemd=always) does the cgroup subtree delegation and tmpfs
plumbing inside the launching user’s namespace. Container root maps to the
engineer’s unprivileged host UID. Requires a cgroup v2 host with delegation.
| Image | Tag | Base image |
|---|---|---|
bisos/deb12-rootless-sysd-vnc-xfce | 1 | bisos/deb12-fresh-vnc-xfce:1.21 |
bisos/deb13-rootless-sysd-vnc-xfce | 1 | bisos/deb13-fresh-vnc-xfce:4 |
Host prerequisites (Debian): podman crun uidmap fuse-overlayfs
dbus-user-session slirp4netns passt. Enable cgroup v2 controller delegation
for the user session (Delegate=cpu io memory pids drop-in on
user@.service).
Two run paths for the same image: run.bash is a direct podman run for
quick, one-off testing; the Quadlet *.container unit is the persistent,
per-engineer form (systemctl --user start/enable + loginctl enable-linger)
for the multi-tenant “N engineers on one host” model — it survives logout and
starts at login/boot.
Before building, verify host readiness with podmanHostVerify.cs -i verify
(installed by bisos.dockerProc; prints GO/NO-GO with per-check PASS/WARN/FAIL).
Note: rootless Podman needs a real user session — log in as the user directly
(not su), or crun’s systemd cgroup manager fails with an
sd-bus ... Input/output error.
Which profile works depends on the host cgroup version. You cannot switch v1 <-> v2 at runtime (it is chosen at boot). Check with:
stat -fc %T /sys/fs/cgroup # cgroup2fs = v2 ; tmpfs = v1/hybrid| Profile | cgroup v1 host | cgroup v2 host |
|---|---|---|
| confined | works | works |
| privileged sysd | works via docker-compose.cgv1.yml overlay | works (.yml) |
| rootless-sysd | does not work (no v1 rootless deleg.) | works |
Notes:
- rootless-sysd requires cgroup v2. cgroup v1 has no safe delegation model, so rootless systemd-as-PID-1 and rootless resource limits do not work.
- privileged sysd works on v1 with the
docker-compose.cgv1.ymloverlay: mounts/sys/fs/cgroup:ro+/sys/fs/cgroup/systemd:rwand omitscgroup: host. Still needs--privileged. - RHEL 8 defaults to v1/hybrid; flip with
grubby --update-kernel=ALL --args“systemd.unified_cgroup_hierarchy=1”= + reboot. If the host cannot be rebooted, use the v1 overlay (privileged) or provision a fresh v2 instance (RHEL 9 / Amazon Linux 2023 default to v2).
The rootless-sysd variant’s smoothness depends on the host’s Podman/crun version, which tracks the host Debian release. (The container is Debian 12/13 either way; this is about the machine running podman.)
| Host | Podman | rootless-sysd experience |
|---|---|---|
| Debian 12 | 4.3.1 | works, with two old-Podman bugs accommodated below |
| Debian 13 | 5.4.2 | clean — the accommodations are unnecessary |
On a Debian 12 host (older Podman/crun) two bugs surface; both are worked around in this repo so it still functions:
- Build: buildah’s per-RUN transient scope fails with
sd-bus ... org.freedesktop.systemd1 ... Input/output error.build.bashpasses--isolation=chrootto sidestep it (image layers are identical). - Runtime:
podman execinto the systemd container fails with a nestedcgroup.procs“Permission denied”. Soverify.shis exec-free (host-side checks + an SSH session), and you manage the container over SSH, not exec.
On a Debian 13 host (Podman 5.4.2) both are fixed: --isolation=chroot is
unnecessary (but harmless) and podman exec works. The workarounds are kept
so the same scripts run unchanged on a Debian 12 or 13 host.
Requirements that apply on both (not Podman-version issues):
- cgroup v2 host with controller delegation (rootless needs it).
- a real user login session (not
su) +loginctl enable-lingerfor the persistent Quadlet per-engineer model. - Podman store (graphroot) on a large local disk — not
/if it is near-full, and never NFS (overlay is unreliable on NFS). Set via~/.config/containers/storage.conf. polkit.serviceis masked in the sysd images: it fails inside the Debian guest (no seat/session) and would otherwise leave systemddegraded, regardless of host.
Run podmanHostVerify.cs -i verify (from bisos.dockerProc) to check these
before building.
Setting aside the security and multi-tenant differences: both are
systemd-PID-1 containers, so all systemd service-management features are
identical between them. They diverge only for workloads that reach through
the container into the host kernel/hardware — exactly what --privileged
unlocks and a rootless user namespace cannot grant.
Identical in both (no functional difference): systemd units + dependency
ordering + Restart, socket activation (.socket units, e.g. nginx/Django),
timers, journald, CPU/memory/PID resource limits, running servers/DBs/web apps
(Airflow, Postgres, nginx, gunicorn/uvicorn), Unix sockets, tmpfs on /run,
binding low ports inside the container.
Works in privileged, fails/limited under rootless-sysd:
| Capability a service might need | privileged | rootless-sysd |
|---|---|---|
Load kernel modules (modprobe) | yes | no (hard wall) |
| Mount real/loop/block filesystems; =.mount=/automount units | yes | no (tmpfs/bind/fuse only) |
| systemd =RootImage=/=MountImage=/portable services | yes | no |
Create/access device nodes (mknod, raw disk, GPU) | yes | no |
/dev/kvm nested VMs, /dev/net/tun kernel VPNs | yes | only if device explicitly passed + permitted |
Write sysctl / /proc/sys (e.g. ip_forward) | yes | no (mostly read-only) |
| Kernel networking: bridges, NAT, nftables modules, networkd | yes | limited (slirp4netns/pasta userns) |
| Set the system clock (=settimeofday=/timesyncd stepping) | yes | no (hard wall) |
Device cgroup (DeviceAllow) and block-I/O limits (IOWeight) | yes | no (controllers not delegated) |
Write to /sys (hardware/kernel knobs) | yes | no (mostly read-only) |
| Docker-in-Docker / full nested runtimes | yes | heavily constrained |
Nuance: some rootless gaps can be selectively re-opened by the host
(--device, --cap-add, --sysctl, device-group membership); others can
never be granted to a rootless user (kernel modules, settimeofday, mounting
arbitrary block devices).
Rule of thumb: userland + systemd → both are equal; needs the kernel to do something privileged on real hardware → only the privileged image can.
Every leaf directory supports two build/verify paths that produce the same
result. Which one you use is a workflow choice today; the .spcs path is
scheduled to become the only path later.
- Bash path (legacy, standalone).
./build.bashto build,./verify.shto smoke-test,docker compose up -d/./run.bashto run. Works withoutbisos.dockerProcinstalled. Keepsbro_dockerfilesusable as a standalone repo. - =.spcs= path (current, framework-driven).
./dockerProc.spcs -i containerProc_imageBuild(or./podmanProc.spcsfor rootless-sysd leaves),-i containerProc_instanceUp,-i containerProc_instanceVerify,-i containerProc_instanceDelete, etc. Requirespipx install bisos.dockerProc. Derives engine, profile, ports, and base image from the leaf’s directory path — no per-leaf configuration. Cmnds are organized by noun:image*for the built artefact,instance*for the running container,fullCleanfor a combined teardown.
The two paths coexist. Same Dockerfile, same layers, same running container
— the .spcs wrapper mostly just calls the same underlying docker /
podman commands with the right arguments derived from the path. Later
releases will retire the bash path once the .spcs path has full parity
across all supported flows.
These images are meant to be built and used locally, not pushed to DockerHub.
For the bash path, always use ./build.bash -d (local build); the bare
./build.bash (multi-arch buildx/manifest → DockerHub) path is not the
intended flow here. The .spcs path is local-only by design.
Docker and Podman keep separate image stores. The image format is identical (OCI), but the two engines do **not** share a local cache:
- Docker: root daemon store under
/var/lib/docker/. - Rootless Podman: per-user store under
~/.local/share/containers/storage/.
So an image built under one engine is invisible to the other — even on the
same host, same user. Build each variant with the engine you will run it with:
docker variants (confined, privileged) with docker; the rootless-sysd variant
with podman. To move an already-built image between stores without a registry:
docker save <img> | podman load (or the reverse).
The .spcs path detects if it is running inside a container (via
/.dockerenv, /run/.containerenv, and /proc/1/cgroup markers) and
refuses with a clear error. This package assumes the Container Platform is
a host, not itself a container — nested containers are not a supported
configuration. The bash path does not check, but nested docker/podman
generally does not work without special host setup either.
cd debian/12/confined/vnc/xfce/bisos_deb12-fresh
./build.bash -d # local build (docker) --- the intended flow
# for the rootless-sysd chain, build the base with podman instead:
# podman build -t bisos/deb12-fresh-vnc-xfce:1.21 .cd debian/12/privileged/vnc/xfce/bisos_deb12-sysd
./build.bash -dcd debian/12/rootless-sysd/vnc/xfce/bisos_deb12-rootless-sysd
./build.bash -d # podman build (local)Same pattern for Debian 13 under debian/13/.
The .spcs file is already planted in every leaf. From any leaf, invoke
it directly — the leaf’s path supplies engine, profile, and ports.
cd debian/12/confined/vnc/xfce/bisos_deb12-fresh
./dockerProc.spcs -i containerProc_imageBuild # with layer cache
./dockerProc.spcs -i containerProc_imageBuild --noCache="true"cd debian/12/privileged/vnc/xfce/bisos_deb12-sysd
./dockerProc.spcs -i containerProc_imageBuildcd debian/12/rootless-sysd/vnc/xfce/bisos_deb12-rootless-sysd
./podmanProc.spcs -i containerProc_imageBuildRun ./dockerProc.spcs or ./podmanProc.spcs with no arguments in any leaf
to see the full menu, filtered to just the commands relevant to that leaf.
Cmnds fall into two noun-prefixed groups plus a combined command:
containerProc_image*—imageBuild,imageDeletecontainerProc_instance*—instanceUp,instanceDown,instanceDelete,instanceRestart,instancePs,instanceLogs,instanceExec,instanceVerify,instanceStatuscontainerProc_fullClean—instanceDelete+imageDeletecombined
For the full design and the multi-tenant registrar picture (design-only in
this phase), see
/bisos/git/auth/bxRepos/bisos-pip/dockerProc/README.org.
# confined / privileged (Docker)
cd debian/12/confined/vnc/xfce/bisos_deb12-fresh
docker compose up -d
cd debian/12/privileged/vnc/xfce/bisos_deb12-sysd
docker compose up -d # cgroup v2 host
docker compose -f docker-compose.cgv1.yml up -d # cgroup v1 host
# rootless-sysd (Podman, cgroup v2 host)
cd debian/12/rootless-sysd/vnc/xfce/bisos_deb12-rootless-sysd
./run.bashThe current directory is mounted into the container at /shuttle/this. After a
container is up, ./verify.sh (in each sysd image dir) smoke-tests it from the
host — systemd is PID 1, services active, journald live, ports listening.
| Container | SSH | VNC | noVNC |
|---|---|---|---|
| deb12-confined | 2222 | 5901 | 6901 |
| deb12-privileged | 2223 | 5902 | 6902 |
| deb12-rootless-sysd | 2225 | 5904 | 6904 |
| deb13-confined | 2227 | 5906 | 6906 |
| deb13-privileged | 2224 | 5903 | 6903 |
| deb13-rootless-sysd | 2226 | 5905 | 6905 |
Connect to localhost:<vncPort>. No password required.
Open http://localhost:<novncPort> in a browser.
ssh -p <sshPort> bystar@localhost- Username:
bystarPassword:insecure - Passwordless sudo inside the container.
The Raw-BISOS bootstrap payload (installRawBisos.sh, postRawBisos.sh,
raw-bisos.sh) is baked into every image at ~/raw-bisos — no host
mount needed. Single canonical source at bro_dockerfiles/common/raw-bisos/,
symlinked into each confined leaf’s build context, then COPY-ed into the
image by the confined Dockerfile. Privileged and rootless-sysd variants
inherit ~/raw-bisos via FROM the confined image.
After starting a container, exec/ssh in and run:
docker exec -it <containerName> bash # or: ssh -p <sshPort> bystar@localhost
cd ~/raw-bisos
bash installRawBisos.shThis downloads and runs the standard bxGenesis Raw-BISOS installer. See https://github.com/bxGenesis/start for details.
After installation, run postRawBisos.sh to wire up ~/.bashrc and set
ownership of /shuttle/this.
Updating the payload. To change the bootstrap scripts, edit files under
bro_dockerfiles/common/raw-bisos/ (single canonical location) and rebuild
the confined image. The change flows to privileged and rootless-sysd via
their FROM inheritance — rebuild them too if you want the update to
land in those variants.
bisos.dockerProc(companion pip package): https://github.com/bisos-pip/dockerProc- BISOS Bootstrap: https://github.com/bxGenesis/start
- TigerVNC: https://tigervnc.org
- noVNC: https://novnc.com
