chroot-distro is a lightweight Linux container management utility built around chroot.
Project description
Chroot-Distro
Chroot-Distro is a utility for managing rootful Linux containers in
Termux and on regular Linux hosts. It uses the
host kernel's native chroot and bind mounts (mount --bind) to provide a
high-performance, near-native Linux environment.
Containers are created by pulling Docker/OCI images directly from Docker Hub or any compatible registry — or by extracting a local tarball / OCI image archive. The container filesystem is assembled from the image layers and stored locally, ready to be entered at any time.
Chroot-Distro can also build OCI images from a Dockerfile (no Docker daemon required), storing the result in the local manifest cache or exporting it as a standalone OCI tarball.
Unlike proot-distro (which is
rootless via proot), Chroot-Distro requires root privileges on the host. Mutating commands
automatically re-launch themselves via sudo, doas, pkexec, or su
when needed (see First-run check).
Table of contents
- Introduction
- Commands reference
- How Chroot-Distro works
- Storage layout
- Environment variables
- Shell completions
- Limitations
- Donate
Introduction
Chroot-Distro lets you run a full Linux userland — Ubuntu, Debian,
Alpine, Arch, openSUSE, distroless server images, anything available as
a Docker/OCI image — on top of Termux on a rooted Android device, or on
top of a regular Linux distribution, with native kernel performance,
without the overhead of proot's ptrace interception, and
without a Docker daemon.
Typical use cases:
- Running a desktop-class Linux distribution on a phone or tablet at near-native speed (rooted device required on Termux).
- Disk-intensive and compile workloads (GCC/Clang, Rust, Go) without
prootslowdowns. - Spinning up server software (Nginx, Nextcloud, PostgreSQL, etc.) on Android by reusing the same OCI images you'd run on a server.
- Building custom OCI images from a Dockerfile on-device, without a Docker daemon — and pushing them to Docker Hub, GHCR, or any OCI-compatible registry.
- Trying a distribution non-destructively: install, experiment,
chroot-distro removewhen done.
Installation
Chroot-Distro requires Python 3.10 or newer. There are no third-party
Python dependencies. Because it uses native chroot and bind mounts, the
effective user for mutating operations must be root (see
First-run check).
On Termux (Android)
- Root your device (Magisk, KernelSU, APatch, or similar).
- Install Termux from F-Droid or Termux GitHub Releases.
- Install Chroot-Distro:
pkg install python mount-utils
pip install chroot-distro
From a local checkout:
git clone https://github.com/sabamdarif/chroot-distro
cd chroot-distro
pip install . # regular install
# pip install -e . # editable install for development
On a regular Linux host
# Debian/Ubuntu example:
sudo apt install python3-pip
pip install chroot-distro
# or from a checkout:
git clone https://github.com/sabamdarif/chroot-distro
cd chroot-distro
pip install .
First-run check
On startup, commands that modify containers or mounts verify that the
effective UID is 0. If not, Chroot-Distro re-executes itself using, in
order: sudo, doas, pkexec, or su.
| Situation | Behaviour |
|---|---|
| Default | Auto-elevate when not root. |
--no-elevate or CHROOT_DISTRO_NO_ELEVATE=1 |
Skip elevation; exit with an error if not root. |
| Termux, default | Prefer su (real root) over sudo. |
Termux, --use-sudo or CHROOT_DISTRO_USE_SUDO=1 |
Prefer sudo for elevation. |
list and help do not require root and are never re-executed.
Quick start
# Install Ubuntu 24.04 from Docker Hub
chroot-distro install ubuntu:24.04
# Start a shell inside the container
chroot-distro login ubuntu
# Same thing, using the login alias
chroot-distro sh ubuntu
# Run a single command and exit
chroot-distro login ubuntu -- /bin/uname -a
# List all installed containers
chroot-distro list
# Build and install a custom image from a Dockerfile
chroot-distro build -t myapp:1.0 --install-as myapp ./mycontext
# Publish the built image to a registry
export CD_DOCKER_AUTH=myuser:mypassword
chroot-distro push myuser/myapp:1.0
# Rebuild from scratch (loses all in-container data)
chroot-distro reset ubuntu
# Unmount bindings and end active sessions
chroot-distro unmount ubuntu
# Permanently remove a container (unmounts active sessions first)
chroot-distro remove ubuntu
Commands reference
Every subcommand supports --help (also -h), which prints help text
laid out for the current terminal width.
Global flags (before the subcommand):
| Option | Description |
|---|---|
-h, --help |
Show top-level help. |
--no-elevate |
Do not auto-elevate to root (CHROOT_DISTRO_NO_ELEVATE=1). |
--use-sudo |
On Termux, prefer sudo over su (CHROOT_DISTRO_USE_SUDO=1). |
Short aliases are accepted for many commands (sh → login, rm →
remove, ins → install, etc.); each section below lists them.
install — Install a container
chroot-distro install [OPTIONS] (IMAGE or PATH or URL)
Aliases: add, i, in, ins
Pull a Docker/OCI image and create a container from it, extract a local archive, or download a remote archive over HTTP/HTTPS.
Options:
| Option | Description |
|---|---|
-n, --name NAME |
Custom local container name. Defaults to the image name (without tag/registry) or the archive filename. Must start with a letter or digit; may contain only letters, digits, _, ., -. |
--override-alias NAME |
Same as -n / --name (mutually exclusive). |
-a, --architecture ARCH |
Override target CPU architecture. Accepts native names (aarch64, arm, i686, riscv64, x86_64) or Docker platform strings (linux/arm64, linux/amd64, …). Defaults to the host CPU. |
-q, --quiet |
Suppress non-error output. |
From a Docker/OCI registry
IMAGE is a standard Docker image reference:
| Form | Example |
|---|---|
| Official image | ubuntu:24.04 |
Official, no tag (uses latest) |
alpine |
| User image | myuser/myimage:tag |
| Custom registry | ghcr.io/foo/bar:latest |
Custom registries are detected when the first path component contains
. or : (a hostname). Public images on ghcr.io, quay.io,
registry.gitlab.com, etc. are pulled with an anonymous Bearer token
discovered from each registry's /v2/ challenge.
Private images require credentials. Set CD_DOCKER_AUTH to
username:password (or username:PAT) before running install. The
colon separator is mandatory. PD_DOCKER_AUTH is accepted as a fallback
for compatibility with proot-distro:
export CD_DOCKER_AUTH=myuser:mypassword
chroot-distro install myuser/private-image:tag
export CD_DOCKER_AUTH=myuser:ghp_xxx
chroot-distro install ghcr.io/myorg/private-image:tag
Layers are cached under $BASE_CACHE_DIR/oci_layers/ and reused on
subsequent installs. If the resolved manifest and all layers are already
cached, installation runs fully offline.
Missing layers are downloaded in parallel (default 4 workers). Set
CD_DOWNLOAD_WORKERS to tune concurrency (integer 1–10; values above
10 are capped).
On Termux, list does not elevate privileges. If containers were
installed as root, run once as root to fix legacy manifest permissions:
su -c 'chmod -R a+r $PREFIX/var/lib/chroot-distro/containers/*/manifest.json'
(or reinstall). New installs write manifest.json as world-readable
(0644).
Examples:
chroot-distro install ubuntu:24.04
chroot-distro install alpine:3.21 --name my-alpine
chroot-distro install debian:bookworm --architecture aarch64
chroot-distro install ghcr.io/myorg/myimage:latest
From a local archive
IMAGE can be a path starting with /, ./, ../, or ~. A bare
token like ubuntu is always treated as a Docker image reference.
Two archive formats are supported (auto-detected):
- Plain rootfs tarball — top-level entries form a standard Linux
filesystem (
bin/,etc/,usr/, …). Strip level is scored automatically. Compression: gzip, bzip2, xz, lzma, or uncompressed. Nomanifest.jsonis written (resetandrunare not available). - OCI image layout — archive contains
oci-layoutat its root (as fromdocker saveorskopeo copy oci-archive:). Layers are applied with OCI whiteout semantics;manifest.jsonis written soresetandrunwork like registry installs.
Examples:
chroot-distro install ./alpine-rootfs.tar.gz
chroot-distro install ./myimage.oci.tar --name myimage
From a URL
When an HTTP or HTTPS URL is given instead of a local path, the archive
is downloaded fully and then processed the same way as a local file.
Only http:// and https:// are supported. The default container name
is derived from the last URL path component; use --name to override.
chroot-distro install https://example.com/rootfs.tar.xz --name demo
After installation, if the image defines an Entrypoint, a
Run entrypoint: chroot-distro run <name> hint is printed alongside
Start shell: chroot-distro login <name>.
build — Build an image from a Dockerfile
chroot-distro build [OPTIONS] [PATH]
Build an OCI/Docker-compatible image from a Dockerfile. PATH is the
build context directory (default .); all COPY/ADD source paths are
resolved relative to it.
By default the built image is stored in the local manifest cache under
the tag given by --tag (default <basename(PATH)>:latest). A subsequent
chroot-distro install <tag> installs entirely without network access.
Options:
| Option | Description |
|---|---|
-f, --file PATH |
Dockerfile at PATH instead of <PATH>/Dockerfile. Pass - to read from stdin. |
-t, --tag REF |
Image reference to assign. Repeatable. |
--build-arg K=V |
Set a build-time ARG (only declared ARGs are honoured). Repeatable. |
--architecture ARCH |
Target CPU architecture (default: host). |
--target STAGE |
Stop after the named multi-stage build stage. |
-o, --output FILE |
Write an OCI image-layout tarball to FILE. Compression inferred from the extension. Repeatable. |
--install-as NAME |
After build, install the image as container NAME. |
--no-cache |
Disable per-step build caching. |
-v, --verbose |
Echo each instruction and stream RUN output. |
-q, --quiet |
Suppress non-error output. |
Supported Dockerfile instructions:
FROM (multi-stage, FROM scratch, COPY --from=), RUN (shell,
JSON exec, here-doc), COPY (--from, --chown, --chmod), ADD,
CMD, ENTRYPOINT, ENV, ARG, LABEL, MAINTAINER, USER,
WORKDIR, EXPOSE, VOLUME, STOPSIGNAL, HEALTHCHECK, SHELL,
ONBUILD.
BuildKit-only features (RUN --mount, RUN --network,
RUN --security, COPY --link, COPY --parents) are rejected with an
explicit error.
chroot requirement:
If the Dockerfile contains any RUN instruction, each step executes
inside the in-progress rootfs via chroot and therefore requires root.
Metadata-only builds (COPY/ADD/ENV/… without RUN) run in
pure-Python mode and do not require root.
Examples:
chroot-distro build .
chroot-distro build -t myapp:1.0 --install-as myapp .
chroot-distro build -t myapp:arm64 --architecture aarch64 -o myapp.oci.tar.gz .
chroot-distro build --build-arg HTTP_PROXY=$HTTP_PROXY -t myapp .
Limitations:
RUN steps run under chroot, not a real container runtime: no PID,
network, or IPC isolation, no cgroups, no seccomp. Steps that depend
on real namespaces or kernel features may fail or behave differently from
docker build. Multi-platform manifest lists are not produced.
push — Push a built image to a registry
chroot-distro push [OPTIONS] IMAGE
Upload a locally built image to a Docker/OCI registry. The image must
have been produced by chroot-distro build -t IMAGE first; push reads
the manifest and blobs from the local cache. No Docker daemon is
required.
Options:
| Option | Description |
|---|---|
--architecture ARCH |
Push the manifest built for the given architecture (must match the build). Default: host. |
-q, --quiet |
Suppress non-error output. |
Authentication:
Set CD_DOCKER_AUTH=username:password (colon required). PD_DOCKER_AUTH
is accepted as a fallback:
chroot-distro build -t myuser/myapp:1.0 ./mycontext
export CD_DOCKER_AUTH=myuser:mypassword
chroot-distro push myuser/myapp:1.0
Each layer is HEAD-probed first; existing blobs are skipped. 401/403
responses include a hint to set or fix CD_DOCKER_AUTH.
login — Start a shell inside a container
chroot-distro login [OPTIONS] CONTAINER [-- COMMAND ...]
Aliases: sh
Spawn an interactive shell (or a custom command) inside an installed
container. The -- separator passes arguments to the container's login
shell.
Examples:
chroot-distro login ubuntu
chroot-distro login ubuntu --user myuser
chroot-distro login ubuntu -- /bin/ls /etc
chroot-distro login ubuntu -- bash -c "echo hello"
chroot-distro sh ubuntu
chroot-distro login ubuntu --get-chroot-cmd
Options always available:
| Option | Description |
|---|---|
-u, --user USER |
Log in as USER (default: root). Accepts name, numeric uid, name:group, or uid:gid. |
--isolated |
Reduce host exposure and enable namespace isolation (mount, PID, UTS, IPC via unshare/nsenter). On Termux: also skip Android system, storage, and $PREFIX binds unless you opt in with --shared-* or --bind. On Linux: skip default /tmp and /tmp/.X11-unix unless --shared-tmp or --shared-x11. Mutually exclusive with --minimal. |
--minimal |
Bare minimum chroot: core pseudo-filesystems only (/dev, /proc, /sys, plus /run, /dev/pts, /dev/shm when present). Stripped guest environment. Mutually exclusive with --isolated. |
--shared-home |
Bind the invoking user's host home into the guest home (or /root for root). On Termux, binds TERMUX_HOME. |
--shared-tmp |
Bind host tmp (/tmp on Linux, $PREFIX/tmp on Termux) to /tmp in the guest. On Linux, included by default unless --isolated. |
--shared-x11 |
Bind the host X11 socket directory to /tmp/.X11-unix in the guest. On Linux, also forwards DISPLAY, XAUTHORITY, and XDG_RUNTIME_DIR from the invoking user's session and bind-mounts the authority file when needed. Included by default unless --isolated. On Termux, opt-in only (termux-x11 often works with --shared-tmp alone). |
-b, --bind SRC[:DST] |
Bind-mount a custom host path (repeatable). DST must be an absolute guest path. |
--hostname STRING |
Hostname inside the container (default: localhost). |
-w, --work-dir PATH |
Initial working directory (default: user's home). |
-e, --env VAR=VALUE |
Set a guest environment variable (repeatable). |
--get-chroot-cmd |
Print the fully assembled env + chroot command line and exit. |
Host bindings (Linux, default mode)
Without --isolated or --minimal, host /tmp and /tmp/.X11-unix
(when present) are bind-mounted into the guest. When X11 sharing is
active, DISPLAY, XAUTHORITY, and XDG_RUNTIME_DIR are forwarded from
the invoking user's desktop session; the X authority file is bind-mounted
when it lives outside /run (which is already shared). Compositors such as
niri with xwayland-satellite often authenticate X11 clients by Unix-socket
UID instead of an on-disk cookie; with --shared-x11, chroot-distro aligns
the guest user's UID to the invoking host user without bind-mounting home. If
a cookie file exists but the guest cannot read it, the cookie is copied into
/var/tmp/.chroot-distro-xauthority (requires xauth on the host). If that
fails, use --shared-home, xhost +SI:localuser:GUEST, or a UID-matched
user.
Use --isolated to skip those defaults, or --minimal for only core
pseudo-filesystems. Home is never bind-mounted unless you pass
--shared-home.
Namespace isolation (--isolated)
With --isolated, chroot-distro creates a per-container namespace holder
(unshare) and runs bind mounts, special mounts, and chroot inside that
environment (nsenter). Supported namespaces: mount, PID, UTS,
and IPC (each is used only if the kernel supports it; mount is
required). This is inspired by Ubuntu-Chroot
and is not a full container runtime: there is no network namespace, no
user namespace mapping, and no image layering.
Do not mix --isolated and non-isolated logins on the same container
without running chroot-distro unmount <name> first. Concurrent
--isolated sessions share the same holder and mounts.
Host bindings (Termux, default mode)
Without --isolated or --minimal, the following host paths are
bind-mounted when present and readable:
/apex
/data/app
/data/dalvik-cache
/data/misc/apexdata/com.android.art/dalvik-cache
/data/data/<termux-app-package>
/linkerconfig/com.android.art/ld.config.txt
/linkerconfig/ld.config.txt
/odm
/plat_property_contexts
/product
/property_contexts
/sdcard
/storage/emulated/0
/storage/self/primary
/system
/system_ext
/vendor
For normal-type containers, the Termux $PREFIX is also bound at its
original path so Termux utilities (termux-api, pkg, etc.) remain
reachable inside the guest.
Guest environment
The host environment is not carried into the guest. Precedence (later entries win):
- Baseline:
PATH(fromDEFAULT_PATH_ENV),MOZ_FAKE_NO_SANDBOX=1,PULSE_SERVER=127.0.0.1(Termux only). - Image-defined
Envfrommanifest.json. - Android system vars (
ANDROID_*,BOOTCLASSPATH, …), Termux only, when not--isolatedand not--minimal. - Your
--env VAR=VALUEentries. HOME,USER,TERM(defaultxterm-256color),COLORTERM(when set on the host).- On Linux (unless
--isolatedor--minimal):DISPLAY,XAUTHORITY, andXDG_RUNTIME_DIRwhen X11 sharing is active (default mode or--shared-x11). Your--enventries override these.
On Termux (unless isolated or minimal), $PREFIX/bin is appended to
PATH. A snippet at /etc/profile.d/termux-profile.sh re-applies
login-time variables after the distro's /etc/profile runs, so
su - someuser inside the container does not drop them.
In --minimal mode only your --env entries plus TERM/COLORTERM
are exported.
Session lifecycle
The first login or run for a container performs bind mounts and
increments a session counter. Each exiting session decrements it; when
the counter reaches zero, all bind mounts are unmounted automatically.
Use unmount to force teardown (see below).
run — Run the image-defined entrypoint
chroot-distro run [OPTIONS] CONTAINER [-- ARG ...]
Run the Entrypoint and/or Cmd from the container's OCI manifest
(equivalent to docker run). Requires an OCI install with
manifest.json (plain tarball installs have no recorded
Entrypoint/Cmd).
Entrypoint and Cmd resolution:
| Image | Args after -- |
Inner command |
|---|---|---|
Entrypoint + Cmd |
(none) | Entrypoint + Cmd |
Entrypoint + Cmd |
ARGS |
Entrypoint + ARGS (Cmd replaced) |
Only Cmd |
(none) | Cmd |
Only Cmd |
ARGS |
ARGS (Cmd replaced) |
Only Entrypoint |
(none) | Entrypoint |
Only Entrypoint |
ARGS |
Entrypoint + ARGS |
| Neither | (none) | Error |
| Neither | ARGS |
ARGS |
When --work-dir is not given, run uses the image WorkingDir
(falling back to /).
run accepts the same options as login. See
chroot-distro login --help.
Examples:
chroot-distro run hello-world
chroot-distro run ubuntu -- /bin/echo hi
chroot-distro run nextcloud --get-chroot-cmd
list — List installed containers
chroot-distro list [OPTIONS]
Aliases: li, ls
Show installed containers (subdirectories of containers/ with a
rootfs/). For each container prints rootfs size, image source
(Docker/OCI reference and architecture from manifest.json, or
local archive for plain tarballs), and status (idle or
in use with PID when another command holds the container lock).
Does not require root. When none are installed, an install suggestion is
printed.
| Option | Description |
|---|---|
-q, --quiet |
Print only container names, one per line. |
remove — Delete a container
chroot-distro remove [OPTIONS] CONTAINER
Aliases: rm
Permanently delete the container and all its data. This cannot be undone and is not confirmed.
Before deletion, active mounts are detected via /proc/mounts and
unmounted cleanly. File permissions are fixed on the fly so chmod-000'd
subtrees can always be removed.
| Option | Description |
|---|---|
-v, --verbose |
Log each deleted file. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
unmount — Unmount a container
chroot-distro unmount CONTAINER
Aliases: umount, um
Safely unmount a container's bind mounts. If chroot processes are still
running, SIGTERM is sent (with SIGKILL after two seconds if needed),
the session counter is reset to 0, and all bind mounts are removed.
If a path is busy, a lazy unmount (umount -l) is attempted as a
fallback.
rename — Rename a container
chroot-distro rename OLDNAME NEWNAME
Rename a container from OLDNAME to NEWNAME.
| Option | Description |
|---|---|
-q, --quiet |
Suppress non-error output. |
reset — Reinstall a container from scratch
chroot-distro reset CONTAINER
Remove the container rootfs and reinstall from the image recorded in
containers/<name>/manifest.json. All data inside the container is
lost. Requires an OCI install (plain rootfs tarballs cannot be
re-pulled).
| Option | Description |
|---|---|
-q, --quiet |
Suppress non-error output. |
backup — Archive a container
chroot-distro backup [OPTIONS] CONTAINER
Aliases: bak, bkp
Create a TAR archive containing <name>/manifest.json (when present)
and <name>/rootfs/.
Options:
| Option | Description |
|---|---|
-o, --output FILE |
Write to FILE instead of stdout. Refuses to overwrite an existing file. |
-c, --compress TYPE |
Force compression: gzip, bzip2, xz, or none. |
-v, --verbose |
Log each archived file. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
Compression is inferred from the file extension unless --compress
overrides it. Without --output, the archive goes to stdout
(uncompressed by default); stdout cannot be a TTY.
File ownership in the archive is zeroed. Block/char devices, FIFOs, and
sockets are skipped. backup is TTY-safe when piping into
interactive tools (e.g. gpg -c).
Examples:
chroot-distro backup ubuntu --output ubuntu.tar.xz
chroot-distro backup ubuntu | gzip > ubuntu.tar.gz
chroot-distro backup ubuntu | gpg -c > ubuntu.tar.gpg
restore — Restore a container from a backup
chroot-distro restore [OPTIONS] [BACKUP_FILE]
Restore from a TAR archive. When BACKUP_FILE is omitted, data is read
from stdin. Compression is auto-detected.
Options:
| Option | Description |
|---|---|
-v, --verbose |
Log each extracted file. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
Archive format requirements:
- Files must live under
<name>/manifest.jsonand<name>/rootfs/…. Bare-root archives are rejected. - Without
manifest.json, login still works butresetandrunwill not. - Hard links in the archive are materialised as independent copies.
restore is TTY-safe for interactive pipelines
(gpg -d archive.gpg | chroot-distro restore).
Examples:
chroot-distro restore ubuntu.tar.xz
cat ubuntu.tar.xz | chroot-distro restore
gpg -d ubuntu.tar.gpg | chroot-distro restore
copy — Copy files to or from a container
chroot-distro copy [OPTIONS] [CONTAINER:]SRC [CONTAINER:]DEST
Aliases: cp
Copy files between the host and a container rootfs, or between two
containers. In-container paths use the container:path prefix.
| Option | Description |
|---|---|
-r, --recursive |
Copy directories recursively (preserves symlinks). |
-m, --move |
Move instead of copy (delete source after success). |
-v, --verbose |
Log each copied file. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
Examples:
chroot-distro copy ./file.txt ubuntu:/root/file.txt
chroot-distro copy ubuntu:/etc/resolv.conf ./resolv.conf.bak
chroot-distro copy --recursive ./myapp ubuntu:/opt/myapp
sync — Synchronize files to or from a container
chroot-distro sync [OPTIONS] [CONTAINER:]SRC [CONTAINER:]DEST
Synchronize SRC to DEST, copying only files that differ. Always recursive.
Comparison method:
| Mode | What is compared |
|---|---|
| Default | File size and integer modification time |
--checksum |
File size and CRC32 checksum |
Regular files are written atomically (.~cd_sync temp file →
os.replace). Symlinks are copied as-is. Hard links become independent
copies. Block/char devices, FIFOs, and sockets are skipped.
| Option | Description |
|---|---|
-c, --checksum |
Compare by size + CRC32 instead of size + mtime. |
-d, --delete |
Remove destination entries with no source counterpart. |
-v, --verbose |
Log each synced or deleted entry. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
Examples:
chroot-distro sync ./app ubuntu:/opt/app
chroot-distro sync --checksum ./data ubuntu:/data
chroot-distro sync --delete ./app ubuntu:/opt/app
clear-cache — Delete the download cache
chroot-distro clear-cache
Aliases: clear, cl
Remove every entry from $BASE_CACHE_DIR — layer blobs (oci_layers/),
resolved manifests (oci_manifests/), and the build cache index
(build_cache_index.json). Freed disk space is reported in human-readable
units.
| Option | Description |
|---|---|
-v, --verbose |
Log each deleted file. |
-q, --quiet |
Suppress non-error output. Mutually exclusive with --verbose. |
After clear-cache, the next install or reset of an image requires
network access again.
help — Show command help
chroot-distro help [COMMAND]
Aliases: h, he, hel
Print detailed help for COMMAND, or general usage when omitted.
How Chroot-Distro works
Chroot-Distro is a thin orchestration layer around two primary building blocks:
1. OCI registry client
The install command speaks the OCI Distribution protocol directly over
urllib:
- Public images on Docker Hub need no credentials
(e.g.
ubuntu:24.04). - Public images on other registries use a full reference
(e.g.
ghcr.io/myorg/myimage:tag). - Manifest lists are resolved to the platform matching your CPU (or
--architecture). - Each layer blob is downloaded with SHA-256 verified before entering the cache.
- Layer blobs and the resolved single-arch manifest are cached locally.
Layers are applied in order with full OCI whiteout semantics. After all
layers are applied, Chroot-Distro adds small fixups when /etc/ exists:
/etc/resolv.confis replaced with Google DNS (8.8.8.8 / 8.8.4.4)./etc/hostsgets a minimal localhost mapping.- On Termux, the host Android user is registered as
aid_<name>in/etc/passwd,/etc/group, etc., so Android UID permissions work inside the guest.
The OCI manifest and image config are saved to
containers/<name>/manifest.json for reset and run.
Local archives and HTTP/HTTPS URLs follow the same extraction paths as
in the install section.
2. Native chroot and bind mounts
Unlike proot, which rewrites paths via ptrace, Chroot-Distro uses
real kernel features:
- Bind mounts (
mount --bind) for host directories inside the guest. - Session tracking under
$RUNTIME_DIR/data/<name>/sessions. - Automatic mount/unmount: the first session mounts; the last session exiting unmounts everything.
- Lazy unmount fallback (
umount -l) when a target is busy.
A typical login invocation looks roughly like:
env PATH=… HOME=/root USER=root … \
chroot /…/containers/ubuntu/rootfs \
/bin/sh -c 'cd /root && exec /bin/bash -l'
Add --get-chroot-cmd to print the exact command line without running it.
Cross-architecture support
Guest architectures (aarch64, arm, i686, x86_64, riscv64) are
detected at login by reading ELF headers of common shell binaries. Cross-arch
execution uses QEMU user-mode via binfmt_misc / QEMU user binaries
installed on the host.
Storage layout
All runtime data lives under $RUNTIME_DIR:
- Termux:
$TERMUX__PREFIX/var/lib/chroot-distro/, whereTERMUX__PREFIXdefaults to/data/data/com.termux/files/usr. - Regular Linux:
$XDG_DATA_HOME/chroot-distro/(default~/.local/share/chroot-distro/).
The OCI cache ($BASE_CACHE_DIR) is under $RUNTIME_DIR/cache on
Termux, and under $XDG_CACHE_HOME/chroot-distro/ (default
~/.cache/chroot-distro/) on a regular Linux host.
Because mutating commands run as root after auto-elevation, effective
paths on Linux are typically under /root/.local/share/ and
/root/.cache/ unless you set XDG_DATA_HOME / XDG_CACHE_HOME.
| Path | Contents |
|---|---|
containers/<name>/rootfs/ |
Container root filesystem |
containers/<name>/manifest.json |
Image reference, arch, OCI manifest, image config |
data/<name>/sessions |
Active login / run session counter |
locks/<name>.lock |
Per-container POSIX flock |
locks/build/<key>.lock |
Build/push lock |
$BASE_CACHE_DIR/oci_layers/ |
Cached OCI layer blobs |
$BASE_CACHE_DIR/oci_manifests/ |
Cached single-arch manifests |
$BASE_CACHE_DIR/build_cache_index.json |
Dockerfile build cache index |
Environment variables
| Variable | Effect |
|---|---|
TERMUX__PREFIX |
Override Termux prefix; drives RUNTIME_DIR on Termux. Default: /data/data/com.termux/files/usr. |
TERMUX__HOME |
Override Termux home for --shared-home bindings. Default: /data/data/com.termux/files/home. |
TERMUX_APP__PACKAGE_NAME |
Termux app package (default com.termux); used for /data/data/<pkg>/… binds. |
TERMUX_APP__APP_VERSION_NAME, TERMUX_VERSION |
Either counts toward Termux detection when set. |
XDG_DATA_HOME |
Base for $XDG_DATA_HOME/chroot-distro/ on non-Termux hosts. Default: ~/.local/share. |
XDG_CACHE_HOME |
Base for $XDG_CACHE_HOME/chroot-distro/ on non-Termux hosts. Default: ~/.cache. |
CD_DOCKER_AUTH |
Registry credentials as username:password or username:PAT (colon required). Used by install, build (FROM pulls), and push. PD_DOCKER_AUTH is accepted as a fallback. |
CD_DOWNLOAD_WORKERS |
Parallel registry layer downloads during install (default 4, maximum 10). Invalid values use the default; out-of-range values are clamped. |
CD_DOWNLOAD_RATE_LIMIT |
Bandwidth limit for downloads (e.g., 5M for 5 MiB/s, default 0 = unlimited). Supports suffixes K, M, G (case-insensitive). |
CD_DOWNLOAD_MAX_RETRIES |
Maximum retry attempts per connection failure (default 3, clamped between 0 and 20). |
CD_FORCE_NO_COLORS |
When set, disables ANSI colours in Chroot-Distro output. |
CHROOT_DISTRO_NO_ELEVATE |
When set to 1, disables privilege auto-elevation (same as --no-elevate). |
CHROOT_DISTRO_USE_SUDO |
When set to 1, prefer sudo over su on Termux (same as --use-sudo). |
COLUMNS |
Fallback terminal width for --help rendering. |
TERM, COLORTERM |
Inherited into the guest (always; even in --minimal). TERM defaults to xterm-256color when unset on the host. |
Shell completions
Completion scripts for Bash, Zsh, and Fish live in
src/chroot_distro/completions/:
chroot-distro.bash_chroot-distrochroot-distro.fish
They complete subcommands, global flags (--no-elevate, --use-sudo),
and per-command options (including login/run flags such as
--shared-home, --get-chroot-cmd, and Termux-only
--isolated / --minimal).
If your shell does not pick them up automatically, install them manually:
# Bash
mkdir -p ~/.local/share/bash-completion/completions
cp src/chroot_distro/completions/chroot-distro.bash \
~/.local/share/bash-completion/completions/chroot-distro
# Zsh (add fpath before compinit in ~/.zshrc)
mkdir -p ~/.zsh/completions
cp src/chroot_distro/completions/_chroot-distro ~/.zsh/completions/_chroot-distro
# Fish
mkdir -p ~/.config/fish/completions
cp src/chroot_distro/completions/chroot-distro.fish \
~/.config/fish/completions/chroot-distro.fish
Limitations
Kernel and chroot limitations
- Root required: real
chrootand bind mounts need appropriate privileges; there is no rootless mode. - No real init:
systemd, socket-activated supervisors, and full init systems generally do not work. Individual long-running processes are fine. - Kernel features: FUSE modules, real
iptables, custom cgroup hierarchies, and similar kernel-module features may not work inside the guest. - Namespaces:
--isolatedprovides mount/PID/UTS/IPC isolation viaunshare/nsenter, but there is no network namespace and no parity with Docker or Podman. - Bind mount hygiene: crashed sessions or orphan processes can leave
mounts busy;
unmountand lazy unmount mitigate this but orphaned processes should be cleaned up.
Chroot-Distro limitations
- Termux requires root: unlike proot-distro, Chroot-Distro cannot run containers on a non-rooted Android device.
- Registry authentication: private pulls and pushes need
CD_DOCKER_AUTH=user:password(orPD_DOCKER_AUTH). Dockerconfig.jsoncredential helpers are not read. - Dockerfile builds are not BuildKit:
RUNexecutes underchroot, not a real container runtime. BuildKit-only Dockerfile features are rejected. Multi-platform manifest lists are not produced — build and push once per architecture. pushis single-arch: no manifest-list assembly, cross-repo blob mounting, or chunked uploads.- No live state migration:
backup/restorecapture the rootfs and manifest, not in-memory process state.
Donate
If this project is useful to you, tips in cryptocurrency are welcome:
Bitcoin
13Q7xf3qZ9xH81rS2gev8N4vD92L9wYiKH
Ethereum / USDT (BEP20, ERC20)
0x1d216cf986d95491a479ffe5415dff18dded7e71
USDT (TRC20)
TCjRKPLG4BgNdHibt2yeAwgaBZVB4JoPaD
Dogecoin
DJkMCnBAFG14TV3BqZKmbbjD8Pi1zKLLG6
Issues and contributing
- Bug reports: https://github.com/sabamdarif/chroot-distro/issues
- License: GPL-3.0-only. See LICENSE.
Acknowledgments
- proot-distro — architecture and CLI design inspiration.
- pyLoad — sliding-window speed tracking, token-bucket rate limiting, and connection resilience algorithms.
- distrobox - shared-display option improvements
- Magisk-Modules-Alt-Repo/chroot-distro
- ravindu644/Ubuntu-Chroot
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chroot_distro-2.0.8.tar.gz.
File metadata
- Download URL: chroot_distro-2.0.8.tar.gz
- Upload date:
- Size: 280.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e00d3b93ae98c7ad1ee191951b75f13adedd00fee6dd703a218220766f9ccda
|
|
| MD5 |
99e5338ac0d902fdd765fbbc6d9f8016
|
|
| BLAKE2b-256 |
fa3e4b140b6c2c982d15a6c77b92f4e00a6c36d438f5ee46ef12f9c764a3d486
|
Provenance
The following attestation bundles were made for chroot_distro-2.0.8.tar.gz:
Publisher:
publish.yml on sabamdarif/chroot-distro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chroot_distro-2.0.8.tar.gz -
Subject digest:
3e00d3b93ae98c7ad1ee191951b75f13adedd00fee6dd703a218220766f9ccda - Sigstore transparency entry: 1765586060
- Sigstore integration time:
-
Permalink:
sabamdarif/chroot-distro@0424395019273c6fd37ad112d10b6630608f206f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sabamdarif
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0424395019273c6fd37ad112d10b6630608f206f -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file chroot_distro-2.0.8-py3-none-any.whl.
File metadata
- Download URL: chroot_distro-2.0.8-py3-none-any.whl
- Upload date:
- Size: 201.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e91533bee3cc29330c020cc8adad4821b329564f170600e3bb5a5de9b846420
|
|
| MD5 |
e8cfd9e2475636900708006f2c26f635
|
|
| BLAKE2b-256 |
eb9c28459f299f4f6bbab43548667ef19c5f5bf8d9ca0d364b26b4e3adfea07c
|
Provenance
The following attestation bundles were made for chroot_distro-2.0.8-py3-none-any.whl:
Publisher:
publish.yml on sabamdarif/chroot-distro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
chroot_distro-2.0.8-py3-none-any.whl -
Subject digest:
5e91533bee3cc29330c020cc8adad4821b329564f170600e3bb5a5de9b846420 - Sigstore transparency entry: 1765586322
- Sigstore integration time:
-
Permalink:
sabamdarif/chroot-distro@0424395019273c6fd37ad112d10b6630608f206f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sabamdarif
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0424395019273c6fd37ad112d10b6630608f206f -
Trigger Event:
workflow_run
-
Statement type: