Files
ecr/SPEC.md
T

6.9 KiB

ecr - implementation specification

Synopsis

ecr <distro>[:<version>] [-a <arch>] [options] [-- command [args...]]

CLI Interface

Positional Arguments

  • <distro> (required): Distribution name
  • <version> (optional): Distribution version/codename

Options

Flag Default Description
-a, --arch <arch> host arch Target architecture
--bind [path] cwd Directory to overlay-mount
--bind-rw [path] none Read-write bind mount at /root/<basename> (bypasses overlay)
--no-cache false Download fresh tarball, ignore cache
-h, --help - Show help
-V, --version - Show version

File Layout

Cache Directory

~/.cache/ecr/
├── ubuntu-noble-amd64.tar.gz
├── debian-bookworm-arm64.tar.gz
├── arch-latest-riscv64.tar.gz
└── ...

No metadata files. Tarballs are downloaded once and never redownloaded. Users can delete files manually or use --no-cache to fetch fresh.

Config File

~/.config/ecr.yaml:

dns:
  - 1.1.1.1
cache_dir: ~/.cache/ecr

Distro Sources

Distro Version Format URL Pattern
Ubuntu noble, jammy, mantic or 26.04, 25.10, 22.04, latest, lts https://cdimage.ubuntu.com/ubuntu-base/bionic/daily/current/bionic-base-amd64.tar.gz
Debian bookworm, bullseye, sid, latest https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.tar.xz
Arch latest https://geo.mirror.pkgbuild.com/iso/latest/archlinux-bootstrap-x86_64.tar.zst
Alpine 3.20, 3.19, latest, edge https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-minirootfs-3.23.0-x86_64.tar.gz
Fedora 40, 41, rawhide, latest Extract from registry.fedoraproject.org/fedora:<version>

Architecture Mapping

ecr Ubuntu Debian Arch Alpine Fedora
amd64 amd64 amd64 x86_64 x86_64 x86_64
arm64 arm64 arm64 aarch64 aarch64 aarch64
armhf armhf armhf - armv7 -
riscv64 riscv64 riscv64 riscv64 riscv64 riscv64
ppc64el ppc64el ppc64el - ppc64le ppc64le
s390x s390x s390x - s390x s390x

Fedora Container Extraction

For Fedora, download the container image layer and extract:

  1. Query manifest: GET https://registry.fedoraproject.org/v2/fedora/manifests/<version>
  2. Parse manifest, get layer digest
  3. Download layer blob
  4. Extract tarball

Execution Flow

  1. Parse CLI arguments and config file
  2. Resolve distro/version/arch to tarball URL
  3. Check cache for existing tarball
  4. If not cached, download tarball
  5. Create temp directory for extraction
  6. Extract tarball to temp directory
  7. Create namespaces: user, pid, mount, uts
  8. Set up mounts: /proc, /sys (ro), /dev, /dev/pts
  9. Write /etc/resolv.conf with DNS servers
  10. Set up overlay mount for workspace directory
  11. Set environment variables
  12. Exec shell or command in chroot
  13. On exit, clean up temp directory

Namespace Setup

Namespaces (Always Created)

  • user: Map current user to root (UID 0) inside
  • pid: Isolated process tree
  • mount: Private mounts for chroot setup
  • uts: Hostname set to ecr-<distro>-<random>

Network

Host network namespace (no isolation).

User Namespace Mapping

uid_map: 0 <current_uid> 1
gid_map: 0 <current_gid> 1

This makes the user appear as root inside the chroot while remaining unprivileged on the host.

Mounts Inside Chroot

Path Type Options
/proc proc defaults
/sys sysfs ro,nosuid,nodev,noexec
/dev devtmpfs nosuid
/dev/pts devpts nosuid,noexec
/root/ overlay lowerdir=<bind_path>, upperdir=, workdir=
/etc/resolv.conf file written with DNS

QEMU Integration

Foreign Architecture Detection

If --arch differs from host architecture, QEMU is required.

binfmt_misc Check

Before entering chroot, verify binfmt_misc is registered for target architecture by checking /proc/sys/fs/binfmt_misc/qemu-<arch>.

If not registered, error with message:

Error: binfmt_misc not registered for riscv64

Install QEMU user emulation:
  Ubuntu/Debian: sudo apt install qemu-user-static
  Arch: sudo pacman -S qemu-user-static-binfmt
  Alpine: sudo apk add qemu-user-static

QEMU Binary

No action required. Modern qemu-user-static packages register binfmt_misc with the F (fix binary) flag, loading the interpreter into kernel memory. The kernel handles foreign binary execution transparently.

File Handling

Overlay Mount (Default)

By default, the current working directory is mounted as an overlay filesystem at /root/<basename> inside the chroot, where <basename> is the name of the current directory.

Overlay configuration:

  • lowerdir: the source directory (read-only)
  • upperdir: temp directory for modifications
  • workdir: temp directory required by overlayfs

Changes made inside the chroot are written to upperdir and discarded on exit. The host directory is never modified.

Example:

$ cd ~/projects/myapp
$ ecr ubuntu:noble -- make build
# ~/projects/myapp mounted at /root/myapp
# Build artifacts written to overlay, discarded on exit

Read-Write Bind Mount

--bind-rw <path> bypasses overlay and creates a true read-write bind mount at /mnt/<basename>. This modifies the host filesystem directly. Use with caution.

No Mount

--no-bind skips mounting any directory.

DNS

Default DNS server is 1.1.1.1. Configured via /etc/resolv.conf in chroot:

nameserver 1.1.1.1

Override with --dns flag or config file:

dns:
  - 8.8.8.8
  - 8.8.4.4

Environment Variables

Default environment inside chroot:

  • HOME=/root
  • USER=root
  • SHELL=/bin/bash (or /bin/sh if bash unavailable)
  • TERM=
  • PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Host environment is not inherited.

Signal Handling

Forward SIGINT, SIGTERM, SIGHUP, SIGQUIT to child process. Wait for child to exit before cleanup.

Security Requirements

User Namespace Required

ecr requires unprivileged user namespaces. If unavailable (sysctl kernel.unprivileged_userns_clone=0 or AppArmor restrictions), error with:

Error: User namespaces not available

Enable with:
  sysctl -w kernel.unprivileged_userns_clone=1

Or check AppArmor profile restrictions.

No Root Fallback

There is no fallback to running as real root. The tool is designed for unprivileged use.

Implementation

Language

Rust.

Dependencies

  • nix: Unix syscall bindings (clone, unshare, mount, chroot, namespaces)
  • serde + serde_yaml: config parsing
  • reqwest: HTTP downloads
  • tar: tarball extraction
  • xz2: xz decompression
  • zstd: zstd decompression
  • flate2: gzip decompression
  • clap: CLI parsing
  • signal-hook: signal handling
  • tempfile: temp directories