Skip to content

The build environment

What is installed, what is not, what the environment carries, and what the container is allowed to touch.

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#

The toolchain
CE C toolchainCEdev v15.0

Installed at /opt/CEdev. cedev-config is on the path, so the standard include $(shell cedev-config --makefile) works unchanged.

ce-libifyfrom toolchain_libify

At /opt/CEdev/bin/ce-libify, with its fasmg macro packages under /opt/CEdev/meta/libify.

fasmg

At /opt/CEdev/bin/fasmg.

The base system
Debian12 slim
make, bash, git
python3standard library only

No 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=1

CEAGLE_FLIGHT is the one to branch on when a build should behave differently here than on your machine:

makefile
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#

Denied
network--network noneRequired

No 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 dropped

Plus no-new-privileges.

rootunprivileged user

Nothing runs as root.

a writable root filesystemread-only

With tmpfs mounted for /tmp and the home directory. The working tree is writable.

Limits#

Ceilings
wall clock30s to 30mdefaults to 10m

Set with the build timeout in the manifest. The container is killed at the limit.

memorypinned

Memory with swap pinned to the same value, so a build cannot escape into swap.

processescapped

A pid ceiling, so a fork bomb dies rather than the host.

artifactat most 8 MiB

And the variable body has to fit in a calculator variable.

library archiveat most 4 MiB
log24 KiB

Truncated, 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.

How a build is contained

What your build can and cannot reach while it runs, and why.

Read on

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.

CEagle documentation