ceamac be5a0b8202 Add a local YAML config file for persistent settings
Reads $XDG_CONFIG_HOME/slocker-lite/config.yaml (falling back to
$HOME/.config/slocker-lite/config.yaml), organized into sections. Only
the "global" section's log-level is supported for now -- other options
are one-shot flags, not standing preferences. An explicit --log-level
on the command line always overrides the config file, the same way
SPDLOG_LEVEL already does.

Uses libyaml directly (yaml_dep was already declared in meson.build but
unused). A missing config file isn't an error; unknown sections/keys
are ignored for forward-compatibility; malformed YAML syntax is a hard
error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-21 12:31:40 +00:00

slocker-lite

slocker-lite mounts an OCI Image Layout tar (the format produced by skopeo, podman save --format oci-archive, or a modern docker save) and runs a sandboxed command against it — without podman, docker, or kernel overlayfs.

Why

The target environment is Android with a stock kernel: podman/docker don't run there (missing namespace support), and there's no kernel overlayfs. slocker-lite works around both: it imports image layers into containers-storage and mounts them with fuse-overlayfs (userspace, no kernel overlayfs needed), then sandboxes the run with bwrap in "degraded mode" — using only whichever --unshare-xxx namespaces the running kernel actually supports, instead of requiring the full set.

Status

Early-stage. Mounting, running, and dropping privileges to a specific user/group all work. Volumes and image-declared networking (Volumes/ExposedPorts/Env from the image config) are parsed but not yet applied, and there's no background/daemonized run mode yet.

Requirements

Build-time:

  • Meson + a C++20 compiler
  • fmt, libarchive, nlohmann_json, yaml-0.1 (libyaml)
  • spdlog (uses the system package if found, otherwise fetched automatically via the vendored subprojects/spdlog.wrap)
  • catch2, only if the enable_tests Meson option is on (default: on)

Runtime:

  • containers-storage
  • fuse-overlayfs
  • bwrap (bubblewrap)
  • nsenter (only needed for non-root runs — see "How it works" below)

Build

meson setup buildDir
meson compile -C buildDir
meson test -C buildDir

This also builds buildDir/slocker-lite-priv-drop, a small statically-linked helper that -r --user/--group needs at runtime (see "How it works").

Usage

slocker-lite -m|--mount <image.tar>
slocker-lite -r|--run <image.tar> [-- <command> [args...]]
slocker-lite -u|--umount <layer-id>
slocker-lite -c|--cleanup <layer-id>
slocker-lite -l|--list-images <directory>
slocker-lite -t|--test
slocker-lite -h|--help
slocker-lite -V|--version
Flag Description
-m, --mount <image.tar> Validate and mount an OCI Image Layout tar.
-r, --run <image.tar> Mount, run bwrap in the foreground, then unmount and clean up on exit. Defaults to the image's own Entrypoint/Cmd (or /bin/sh if neither is set); pass -- <command> [args...] to override.
-u, --umount <layer-id> Unmount a previously mounted layer (the ID printed by --mount/--run, or from containers-storage layers).
-c, --cleanup <layer-id> Delete a layer and its ancestor chain from local storage (unmount it first).
-n, --no-nsenter With --run, bind the mount directly instead of nsenter-ing into fuse-overlayfs's namespace. Automatic when running as root; use this to force it off otherwise.
--user <user> With --run, run the command as this user (name or numeric uid) instead of the image's own declared user (or root, if it declares none). Resolved against the image's own /etc/passwd. Only takes effect when --run executes as root.
--group <group> With --user, use this group (name or numeric gid) instead of the user's primary group.
-l, --list-images <dir> List OCI Image Layout tars (*.tar, *.tar.*) found directly in <dir>, with their name:tag.
-t, --test Print which bwrap --unshare-xxx namespaces the running kernel supports.
--log-level <level> Set log verbosity (trace, debug, info, warn, error, critical, off).
-h, --help Print usage and exit.
-V, --version Print version information and exit.

Examples

# Mount an image and inspect it (prints the merged mount path)
./buildDir/slocker-lite -m myimage.tar

# Mount, run the image's default command, then unmount and clean up
./buildDir/slocker-lite -r myimage.tar

# Run a specific command instead
./buildDir/slocker-lite -r myimage.tar -- /bin/sh -c 'echo hello'

# Run as a specific user (as root only)
sudo ./buildDir/slocker-lite -r myimage.tar --user git

# List every OCI image tar in a directory
./buildDir/slocker-lite -l ./images

Configuration

Persistent settings can be kept in a local YAML config file at $XDG_CONFIG_HOME/slocker-lite/config.yaml (falling back to $HOME/.config/slocker-lite/config.yaml if XDG_CONFIG_HOME isn't set). The file is organized into sections; only global exists today:

global:
  log-level: debug

Only options that make sense as a standing preference are supported here — right now just log-level (one-shot commands like --mount/--run/--user don't belong in a config file). A missing config file is fine (nothing is overridden); an explicit --log-level on the command line always overrides the config file.

How it works

Image layers are imported into containers-storage (parent-chained) and the resulting top layer is mounted via fuse-overlayfs; -r/--run then sandboxes the requested command with bwrap. Because containers-storage mount runs rootless by re-execing into a private user+mount namespace, -r/--run normally has to nsenter into that namespace to reach the mount — except when running as root, where the mount is already directly visible and --unshare-user is skipped entirely (a fresh user namespace isn't needed for root's own privilege, and forces an unrelated supplementary-group bug in that case). Running as root also unlocks --user/ --group: since bwrap --uid/--gid require a user namespace that isn't available there, slocker-lite instead bind-mounts a separate, statically-linked helper (slocker-lite-priv-drop) into the sandbox and routes the command through it to drop privileges before exec.

See CLAUDE.md for the full architecture writeup (file-by-file breakdown, the reasoning behind each of the above, and known gaps).

License

GPL-2.0-or-later. See COPYING.

S
Description
Run a docker image with bubblewrap on low end devices
Readme 772 KiB
Languages
C++ 97.9%
Python 1%
Meson 0.7%
C 0.3%