SHA256
1
5
Fork
You've already forked esquema
0
Esquema is a minimal, security-first container runtime, built natively in Scheme. https://git.securityops.co/cristiancmoises/esquema
  • C 56.9%
  • Scheme 39.6%
  • Makefile 3.5%
Berkeley 9ed8d60152 examples: website-hosting tutorial + build-web-rootfs helper
Make it easy for others to build and host sites with Esquema:
- README: new "Tutorial: host a website with Esquema" section — build a
 rootfs, describe the deployment as a <container>, run it, verify, and the
 privileged-port (<1024) sysctl note.
- examples/build-web-rootfs.sh: assemble a BusyBox web rootfs (httpd + full
 applet userland, mount points, minimal /etc), locating busybox from the
 store or `guix build`.
- examples/deploy-web.scm: serve examples/rootfs-web with busybox httpd inside
 an Esquema container, sharing the host net so it is reachable, binding
 /gnu/store read-only for busybox's libraries; port via $ESQ_PORT.
- examples/rootfs-web: a polished landing page for the demo.
- .gitignore: keep the page + layout, ignore the regenerable busybox binary,
 its applet symlinks and run-time mount targets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026年07月04日 11:30:15 -03:00
c Harden Esquema into a secure rootless container runtime 2026年07月01日 22:14:09 -03:00
examples examples: website-hosting tutorial + build-web-rootfs helper 2026年07月04日 11:30:15 -03:00
scheme/esquema Harden Esquema into a secure rootless container runtime 2026年07月01日 22:14:09 -03:00
.gitignore examples: website-hosting tutorial + build-web-rootfs helper 2026年07月04日 11:30:15 -03:00
LICENSE Initial commit 2026年02月08日 22:36:30 +00:00
Makefile Harden Esquema into a secure rootless container runtime 2026年07月01日 22:14:09 -03:00
manifest.scm Harden Esquema into a secure rootless container runtime 2026年07月01日 22:14:09 -03:00
README.md examples: website-hosting tutorial + build-web-rootfs helper 2026年07月04日 11:30:15 -03:00

Esquema — a Guile-native container runtime

Esquema is a minimal, security-first, rootless container runtime built natively in Scheme. It integrates with GNU Guix and Shepherd to give reproducible, strongly-isolated environments without a daemon, without root, and without YAML — just declarative Scheme.

Containers are first-class Scheme objects. Isolation is explicit, fine-grained and secure by default.


Security model

Esquema confines an untrusted payload behind defence-in-depth. Every layer is applied, in the correct order, inside a clone/fork child that runs only async-signal-safe C between fork and execve:

  1. User namespace (rootless). Runs as an unprivileged user; the payload is root inside the namespace, mapped to your ordinary uid outside.
  2. Mount / PID / UTS / IPC / net / cgroup namespaces. Full process, filesystem, hostname, IPC and network isolation.
  3. pivot_root into the rootfs, with the host mount tree detached so it is unreachable, a fresh /proc, and minimal /dev.
  4. All capabilities dropped — bounding set emptied, ambient cleared, capset zeroed, securebits locked (SECBIT_NOROOT, so uid-0-in-ns confers nothing), and PR_SET_NO_NEW_PRIVS set so setuid binaries cannot escalate.
  5. seccomp-BPF allowlist — default ENOSYS, with a KILL_PROCESS deny-set for ptrace, mount, unshare, setns, bpf, keyctl, module and kexec syscalls, etc. Non-native (incl. x32) ABIs are killed.
  6. cgroups v2 limits (best-effort under rootless delegation) — memory.max, pids.max, cpu.max.

This is verified by the test-suite (see Testing): the payload cannot see host PIDs, cannot reach the host filesystem, has an empty capability set, is killed on a denied syscall, and sees only loopback networking.

How this compares

Property Esquema Docker/Podman Jails / LXC guix shell
Language-native (Scheme)
Daemon-free (Docker)
Rootless by default partial
User namespace isolation n/a (BSD) only --container
seccomp syscall filtering (allowlist) (coarse)
Capability drop + no-new-privs partial
Declarative in one language (Scheme) (YAML/OCI) (Guix)

A plain guix shell gives a reproducible environment but no kernel-level isolation (shared namespaces, no seccomp, full capabilities). Esquema adds the isolation while keeping the Scheme-native, daemon-free ergonomics. Container startup is ~13 ms (≈8 ms over a bare execve).


Getting started

Build the shared library and run the smoke test inside the pinned environment:

guix shell -m manifest.scm -- make smoke

Build a self-contained demo rootfs and launch a container:

guix shell -m manifest.scm -- sh examples/build-rootfs.sh examples/rootfs-min
guix shell -m manifest.scm -- env ESQUEMA_LIBDIR=$PWD \
 guile -L scheme examples/hello.scm

Define and run a container from Scheme:

(use-modules (esquema runtime) (esquema container))
(define box
 (make-container "web" "/path/to/rootfs"
 (list "/bin/httpd" "-p" "8080")
 #:hostname "web"
 #:rootfs-ro? #t ; seal the root
 #:limits (make-limits (* 256 1024 1024) ; memory.max bytes
 128 ; pids.max
 50000 100000))) ; cpu 50%
;; forks, isolates, execve()s the payload, waits, returns the exit status:
(run-container box)

make-container is secure by default: all namespaces, seccomp on, and every capability dropped unless you opt out (#:seccomp? #f, #:drop-caps? #f, #:namespaces '(user mount pid ...)).


Tutorial: host a website with Esquema

Serve a real, browser-reachable website from inside an isolated Esquema container — rootless, seccomp-filtered, every capability dropped. Three steps.

1. Build a web rootfs

A rootfs is just a directory. The helper populates one with a full BusyBox userland (its httpd applet is the web server) plus the standard mount points:

examples/build-web-rootfs.sh examples/rootfs-web
# drop your own files in examples/rootfs-web/www/ (index.html, assets, ...)

BusyBox on Guix is dynamically linked, so the container bind-mounts /gnu/store read-only at run time to resolve its loader and libraries — no copying, and the store is world-readable already. (For a fully self-contained image, put a statically-linked server in bin/ instead and skip the bind.)

2. Describe the deployment

A container is a value. examples/deploy-web.scm:

(use-modules (esquema runtime) (esquema container))
(define port (or (getenv "ESQ_PORT") "8081"))
(run-container
 (make-container "esquema-web" "/absolute/path/to/examples/rootfs-web"
 (list "/bin/httpd" "-f" "-v" "-p" port "-h" "/www")
 #:hostname "esquema-web"
 ;; Drop 'net to SHARE the host network so the port is reachable;
 ;; keep it to isolate networking (then only loopback exists).
 #:namespaces '(user mount pid uts ipc cgroup)
 #:mounts '(("/gnu/store" "gnu/store" #t)) ; ro: busybox libs
 #:limits (make-limits (* 128 1024 1024) 64 #f #f)))

Everything except networking stays isolated: the payload is PID 1 in its own PID/mount/UTS/IPC namespace, pivot_root-ed into the rootfs, with an empty capability set and the seccomp filter loaded.

3. Run it

# from a checkout (library on ESQUEMA_LIBDIR):
ESQ_PORT=8081 guix shell -m manifest.scm -- \
 env ESQUEMA_LIBDIR=$PWD guile -L scheme examples/deploy-web.scm
# or, once installed from the securityops channel (guix install esquema):
ESQ_PORT=8081 guile examples/deploy-web.scm

Verify and see the isolation the visitor's server runs under:

curl -s http://localhost:8081/ | head # your page
# what the server process itself sees:
guile -c '(use-modules (esquema runtime)(esquema container))
 (run-container (make-container "x" "'$PWD'/examples/rootfs-web"
 (list "/bin/sh" "-c" "id -u; hostname; grep -E \"Cap|Seccomp\" /proc/self/status; ls /")
 #:mounts (quote (("/gnu/store" "gnu/store" #t)))))'
# -> uid=0 (in-ns) host=x CapEff 0000000000000000 Seccomp: 2 / = just the rootfs

Ports below 1024

A rootless container can only bind an unprivileged port (≥ 1024 by default), so 8081 works out of the box but 80/81 do not. To serve on a privileged port, lower the threshold once (reversible), then use it:

sudo sysctl -w net.ipv4.ip_unprivileged_port_start=81 # revert with =1024
ESQ_PORT=81 guile examples/deploy-web.scm

To keep it running across reboots, wrap it in the Shepherd service (below) or a guix home Shepherd service.


Testing

guix shell -m manifest.scm -- make check # C tests + functional + security + cppcheck
guix shell -m manifest.scm -- make test-perf # startup latency / overhead / leak guard
guix shell -m manifest.scm -- make static # gcc -fanalyzer
guix shell -m manifest.scm -- make sanitize # ASan + UBSan build and run

The suite covers functional behaviour (scheme/esquema/tests/functional.scm), isolation and escape attempts with positive+negative controls (security.scm), C-level enforcement including the seccomp SIGSYS kill (tests/c/test_primitives.c), and performance/leak guards (performance.scm). The C library builds warning-clean under -Wall -Wextra -Werror with FORTIFY, stack-protector/clash protection, full RELRO, a non-executable stack and CF-protection.


Guix service

(esquema esquema-service) provides a Shepherd service type to supervise a container as part of a Guix system configuration.

Esquema empowers secure, reproducible, declarative containerization for GNU Guix — for development, CI/CD and lightweight server deployments.