A Podman-based dev container manager for rootless Fedora/ostree systems.
Each instance is a named environment with its own home directory, customizable Dockerfile, and TOML config. Two base image types are supported:
- scratch — empty image with Fedora ostree symlinks; host
/usr,/etc, etc. are bind-mounted read-only. No image bloat, instant startup. - fedora — full
fedora:latestimage. Self-contained, own package manager, no host mounts.
# Clone and run the install script
git clone https://github.com/djoyce/scratch-monkey.git
cd scratch-monkey
./install.sh
# With GUI support:
./install.sh --guiThe install script checks for uv and offers to install it if missing, then installs scratch-monkey into ~/.local/bin.
Manual install (without install.sh)
uv tool install --editable .
# With GUI support:
uv tool install --editable ".[gui]"Ensure ~/.local/bin is in your PATH.
# Create an instance (scratch base)
scratch-monkey create myproject
# Create a fedora-based instance with shell configs copied in
scratch-monkey create myproject --fedora --skel
# Enter an interactive shell
scratch-monkey enter myproject
# List all instances
scratch-monkey listscratch-monkey create <name> # scratch base
scratch-monkey create <name> --fedora # fedora base
scratch-monkey create <name> --skel # copy /etc/skel dotfiles into home
scratch-monkey clone <source> <dest> # copy Dockerfile + config, fresh home/
scratch-monkey delete <name> # prompts for confirmation (--yes to skip)
scratch-monkey list # show all instances with status
scratch-monkey skel <name> # copy /etc/skel dotfiles (post-create)
scratch-monkey edit <name> # edit scratch.toml ($EDITOR)
scratch-monkey edit <name> --file dockerfile
scratch-monkey edit <name> --file envEach instance lives at $HOME/scratch-monkey/<name>/:
myproject/
home/ ← container home directory (mounted read-write)
Dockerfile ← customizable, extends the base image
scratch.toml ← instance configuration
.env ← environment variables / secrets (KEY=VALUE, one per line)
scratch-monkey enter <name> # interactive shell
scratch-monkey enter <name> --root # root shell
scratch-monkey run <name> # same as enter
scratch-monkey run <name> --wayland # override: enable Wayland
scratch-monkey run <name> --ssh # override: enable SSH agent
scratch-monkey run <name> --cmd /bin/zsh
scratch-monkey start <name> # start overlay container (headless)
scratch-monkey stop <name> # stop overlay containerAll fields are optional — commented-out defaults are shown when an instance is created.
| Field | Type | Default | Description |
|---|---|---|---|
cmd |
string | /bin/bash |
Command to run on entry |
wayland |
bool | false |
Mount Wayland socket, set WAYLAND_DISPLAY |
ssh |
bool | false |
Forward SSH_AUTH_SOCK for SSH agent access |
home |
string | "" |
Override the instance home/ dir with a custom path |
volumes |
list | [] |
Extra volume mounts (host:container:mode) |
env |
list | [] |
Extra environment variables (KEY=value) |
shared |
list | [] |
Shared volume names (mounted at /shared/<name>; append :ro for read-only) |
overlay |
bool | false |
Enable overlay mode (persistent writable layer) |
gpu |
bool | false |
Enable GPU passthrough (auto-detects /dev/dri, /dev/kfd, /dev/nvidia*) |
devices |
list | [] |
Extra device paths to mount (e.g. /dev/video0, /dev/bus/usb) |
Example:
cmd = "/bin/zsh"
wayland = true
ssh = true
volumes = ["/home/linuxbrew/.linuxbrew:/home/linuxbrew/.linuxbrew:ro"]
shared = ["comms", "data:ro"]
overlay = true
gpu = true
devices = ["/dev/video0"]# Build the base image
scratch-monkey build # scratch base
scratch-monkey build --fedora # fedora base
# Build an instance's custom Dockerfile (tagged as the instance name)
scratch-monkey build-instance <name>Auto-builds the base if it's missing when you run enter.
When overlay = true in scratch.toml, a persistent daemon container (<name>-overlay) is kept between sessions. Package installs (e.g. dnf install vim) survive across runs without rebuilding the image.
scratch-monkey enter myproject # exec into the overlay container
scratch-monkey enter myproject --root # exec as root
scratch-monkey reset myproject # wipe the overlay container (prompts)Note: Overlay user setup (
useradd,sudoers) is only performed for fedora-based instances. Scratch instances mount/etcfrom the host read-only — the user already exists and sudo works via host config.
Root entry:
--rootsets the workdir to/root. Scratch images don't create/root, so scratch-monkey handles it automatically: in non-overlay mode,/rootis a tmpfs mount; in overlay mode,/rootis created in the container's writable layer on first root entry and persists across restarts.
Shared volumes let multiple instances share a host directory, useful for IPC via files, sockets, FIFOs, or databases. They live at $HOME/scratch-monkey/.shared/<name>/ and are mounted at /shared/<name> inside each container.
scratch-monkey share create comms # create the shared volume
scratch-monkey share add comms agent1 # add to instance config
scratch-monkey share add comms agent2
# Both instances now see /shared/comms/ (read-write, same host dir)
scratch-monkey enter agent1
scratch-monkey enter agent2
scratch-monkey share list # list volumes and which instances use them
scratch-monkey share remove comms agent1 # remove from instance config
scratch-monkey share delete comms # delete the volume directoryShared volumes default to read-write. Append :ro in scratch.toml to mount read-only:
shared = ["comms", "data:ro"]Make a command from an instance available on your host PATH:
scratch-monkey export myproject /usr/bin/rg # creates ~/.local/bin/rg
scratch-monkey export myproject /usr/bin/rg myrg # custom bin name
scratch-monkey unexport rgThe generated script:
- If already inside the instance, execs the command directly
- If the overlay container is running, execs into it
- Otherwise, launches a one-shot
podman run --rm
A graphical interface is available when installed with GUI dependencies:
uv tool install --editable ".[gui]"
scratch-monkey-gui # standalone launcher
scratch-monkey gui # or via the CLI (respects --instances-dir)
scratch-monkey --instances-dir /path/to/dir gui # use a custom instances directoryThe GUI provides:
- Instance list with start/stop toggle buttons and image/overlay status
- Quick-open buttons to open instance path or home directory in a file manager or terminal
- Configuration editing (cmd, wayland, ssh, home, overlay, GPU passthrough)
- Volume mount management (add/remove host:container mounts, set ro/rw mode; defaults to ro)
- Environment variable, device, and shared volume management
- Action buttons (enter, enter as root, build, reset overlay, export cmd, edit files)
- Live status polling with in-place overlay/image status updates
RUN does not work (no shell in the image). Use COPY/ADD or multi-stage builds:
FROM golang:latest AS builder
RUN go install github.com/some/tool@latest
FROM scratch_monkey
COPY --from=builder /go/bin/tool /usr/local/bin/toolFull OS available — RUN, COPY, ADD all work:
FROM scratch_monkey_fedora
RUN dnf install -y git vim neovimBuild with scratch-monkey build-instance myproject, then run normally.
| Host path | Container path | Mode |
|---|---|---|
/usr |
/usr |
read-only |
/etc |
/etc |
read-only |
/var/usrlocal |
/var/usrlocal, /usr/local |
read-only |
/var/opt |
/var/opt |
read-only |
Instance home/ |
/home/$USER |
read-write |
| (tmpfs) | /tmp |
read-write, in-memory |
| Host path | Container path | Mode |
|---|---|---|
Instance home/ |
/home/$USER |
read-write |
Fedora instances use their own filesystem — no host system mounts.
| Command | Description |
|---|---|
create <name> |
Create a new instance (--fedora, --skel) |
clone <src> <dest> |
Clone an instance (fresh home) |
delete <name> |
Delete an instance (--yes to skip confirm) |
list |
List all instances with status |
skel <name> |
Copy /etc/skel dotfiles into home |
edit <name> |
Edit instance files (--file config|dockerfile|env) |
build |
Build the base image (--fedora) |
build-instance <name> |
Build an instance's Dockerfile |
run <name> |
Run an instance (--root, --wayland, --ssh, --cmd) |
enter <name> |
Interactive shell (--root for root) |
start <name> |
Start overlay container (creates if needed) |
stop <name> |
Stop overlay container |
reset <name> |
Remove overlay container (--yes to skip confirm) |
export <name> <cmd> [bin] |
Export a command to ~/.local/bin |
unexport <bin> |
Remove an exported command |
share create <name> |
Create a shared volume |
share delete <name> |
Delete a shared volume |
share add <vol> <instance> |
Add volume to instance config |
share remove <vol> <instance> |
Remove volume from instance config |
share list |
List all shared volumes and usage |
gui |
Launch the GUI (requires [gui] extras) |
Global options (before the command):
--instances-dir TEXT Default: ~/scratch-monkey
--base-image TEXT Default: scratch_monkey
- Requires rootless podman
- Uses
--userns=keep-idfor correct user ID mapping (dropped for--root) - SELinux labeling disabled for the container (
--security-opt label=disable) - Host networking enabled (
--network=host) - Container hostname is set to
<instance>.<hostname>for prompt clarity
uv is required. The install.sh script installs it if missing.
uv tool install --editable . # install CLI
uv run pytest # unit tests, no real podman needed
uv run --all-extras pytest # include GUI smoke tests
uv run ruff check src tests # lint
uv run ruff format src tests # formatSee justfile for the full list of dev recipes (just test, just lint, just fmt, etc.).
