# slocker-lite `slocker-lite` mounts an [OCI Image Layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md) 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. Named volumes (`-v/--volume`) can be created and are persisted in the config file, but aren't consumed by `-r/--run` yet. Image-declared networking (`ExposedPorts`/`Env` from the image config, and the image's own separately-declared `Volumes`) 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 ```sh 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 slocker-lite -r|--run [-- [args...]] slocker-lite -u|--umount slocker-lite -c|--cleanup slocker-lite -l|--list-images slocker-lite -v|--volume slocker-lite --list-volumes slocker-lite -t|--test slocker-lite -h|--help slocker-lite -V|--version ``` | Flag | Description | | --- | --- | | `-m, --mount ` | Validate and mount an OCI Image Layout tar. | | `-r, --run ` | 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 `-- [args...]` to override. | | `-u, --umount ` | Unmount a previously mounted layer (the ID printed by `--mount`/`--run`, or from `containers-storage layers`). | | `-c, --cleanup ` | 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 ` | 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 ` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. | | `-l, --list-images ` | List OCI Image Layout tars (`*.tar`, `*.tar.*`) found directly in ``, with their `name:tag`. | | `-v, --volume ` | Create a named volume mapped to a host directory (created if missing), recorded in the config file's `volumes` section. Fails if the name or directory is already used by an existing volume. | | `--list-volumes` | List all named volumes (see `-v/--volume`) with their host directory. | | `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. | | `--log-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 ```sh # 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 # Create a named volume backed by a host directory ./buildDir/slocker-lite -v mydata ~/slocker-volumes/mydata # List all named volumes ./buildDir/slocker-lite --list-volumes ``` ## 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: ```yaml global: log-level: debug volumes: mydata: /home/user/slocker-volumes/mydata ``` `global.log-level` is the only standing preference supported today (one-shot commands like `--mount`/`--run`/`--user` don't belong in a config file). An explicit `--log-level` on the command line always overrides the config file. The `volumes` section is managed by `-v/--volume` (see above) rather than hand-edited — it's not consumed by `-r/--run` yet. A missing config file is fine either way (nothing is overridden, and one gets created the first time `-v/--volume` is used). ## 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`](COPYING).