Every build runs in the same image, with the same limits. There is no way to install a package, add an environment variable, reach the network or change the image.
That is what makes a build reproducible from the repository alone.
What is installed#
CE C toolchainCEdev v15.0Installed at
/opt/CEdev.cedev-configis on the path, so the standardinclude $(shell cedev-config --makefile)works unchanged.ce-libifyfrom toolchain_libifyAt
/opt/CEdev/bin/ce-libify, with its fasmg macro packages under/opt/CEdev/meta/libify.fasmgAt
/opt/CEdev/bin/fasmg.
Debian12 slimmake, bash, gitpython3standard library onlyNo pip packages, and no network to fetch any.
curl, xz-utilspresent but useless offline
The environment#
CEDEV=/opt/CEdev
PATH=/opt/CEdev/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/builder
CEAGLE_FLIGHT=1CEAGLE_FLIGHT is the one to branch on when a build should behave differently here than on your
machine:
NAME = SNAKE
ICON = icon.png
ifdef CEAGLE_FLIGHT
COMPRESSED = YES
endif
include $(shell cedev-config --makefile)The toolchain also exports itself from /etc/profile.d, which is why the build command runs through
a login shell.
How your command is invoked#
bash -lc "<your [build] command>"In the directory holding the project's manifest, which is the repository root unless the project
was connected with a manifest path. The
whole repository is checked out either way, so a build in a subdirectory can still read what sits
beside it. It runs as an unprivileged user with uid 2000 named builder.
What the container cannot do#
network--network noneRequiredNo DNS, no HTTP, no package manager, no git fetch. Anything a build needs is in the repository, in a submodule, or in the image.
capabilitiesall droppedPlus
no-new-privileges.rootunprivileged userNothing runs as root.
a writable root filesystemread-onlyWith tmpfs mounted for
/tmpand the home directory. The working tree is writable.
Limits#
wall clock30s to 30mdefaults to10mSet with the build timeout in the manifest. The container is killed at the limit.
memorypinnedMemory with swap pinned to the same value, so a build cannot escape into swap.
processescappedA pid ceiling, so a fork bomb dies rather than the host.
artifactat most 8 MiBAnd the variable body has to fit in a calculator variable.
library archiveat most 4 MiBlog24 KiBTruncated, not failed.
Git#
The clone is shallow, depth one, of the tracked branch.
Git runs with a protocol allow-list on every invocation rather than on the clone alone, because a submodule configuration file is attacker-controlled and is read afterwards. Only HTTPS resolves.
core.symlinks is off, so the checkout contains no links at all. Independently, the artifact
collector refuses anything that is not a regular file inside the tree, because a symlink there would
otherwise be followed and published.
What your build can and cannot reach while it runs, and why.
Making a build reproducible locally#
Install the CE C toolchain and, for a library, ce-libify. Then run exactly the command your manifest names, in a checkout with no extra files:
git clone --depth 1 --branch main https://github.com/ada/snake
cd snake
make
ls -l bin/If that produces exactly one file matching the output pattern, Flight will too.