Project challenges / verified progress
Beacon: package it so anyone can run it

The engineering notebook

Drop root early

What changes when the process inside the container is not root?

Loading statusStage 4 of 9

  • Workspace not ready
  • Agent not ready
Focus25:00
A small focus ritual

0 focus sessions completed. Every fourth session offers a longer break. Start each phase when you are ready.

Study time never unlocks verified lesson progress.

Loading...

Loading verified progress...

Loading GitHub account...
Phase 2 — Runtime trust starts small

Step 01 of 06

Learn the concept

Root inside a container is still a dangerous default. Namespaces reduce what root can see, but a kernel escape, a mounted socket, or an overly generous volume can turn "just container root" into host damage. Beacon does not need that bargain to serve JSON on port 7427.

ROOT CHANGES THE BLASTBug in Beaconattacker writes filesRuns as rootcan alter owned mountslarger blastRuns as 65532only owned paths writesmaller blastRead-only rootwrites need volumesintentional
Non-root is not a force field. It is a narrowing move. Pair it with explicit writable paths and fewer capabilities so a Beacon bug has fewer places to turn into a host problem.
Step 01

The ideas this is made of

Root is a bundle of privileges, not a vibe

UID 0 bypasses normal Unix permission checks and may hold Linux capabilities such as CAP_NET_RAW or CAP_CHOWN. Container runtimes drop many capabilities by default, but not all risk disappears. Running as a numeric non-root UID means ordinary file permissions work against an attacker who only controls the Beacon process.

Kubernetes prefers numbers because names lie

A username like app is resolved through files inside the image. Some minimal images have no /etc/passwd, and different images can map the same name to different IDs. A numeric UID such as 65532 is unambiguous. It also maps cleanly to Kubernetes runAsUser, runAsGroup, and fsGroup later.

Read-only root makes writes visible by design

If the root filesystem is read-only, the process cannot casually write logs, temp files, or SQLite databases into random paths. That sounds annoying because it is doing its job. Beacon should log to stdout and write its database only under a mounted directory such as /var/lib/beacon.

Volumes are permission contracts

A mounted directory is not automatically writable. The UID inside the container must have permission on the mounted path. In local Docker, a named volume is usually easy. Bind mounts from macOS or Linux hosts can expose UID mismatches. The fix is not USER root; it is owning the data path deliberately.

A container denied by permissions
docker run --rm --user 65532:65532 --read-only alpine:3.20 sh -c 'id; touch /root/nope'
# touch: /root/nope: Read-only file system

The exact Alpine error can vary, but the mechanism does not: a non-root user on a read-only filesystem cannot write wherever it feels like writing.

Security knobs that get confused

KnobControlsDoes not control

USER

Default UID and GID

Kernel capabilities by itself

Capabilities

Special kernel privileges

Filesystem ownership

Read-only root

Writes to image layer

Mounted volume writes

Volume

Durable writable path

Application correctness

What these are called on the job

  • UID — The numeric user identity the kernel uses for permission checks.

  • Capability — A slice of root privilege, such as binding low ports or changing ownership.

  • Read-only root — A runtime setting that prevents writes to the image filesystem layer.