hazmat is a tiny Python library + CLI meant to run arbitrary commands through pluggable “sandbox” providers and stream their stdout/stderr as the command executes.
Every sandbox exposes copy_from_host(source, dest) and copy_to_host(source, dest)
to move files/directories across the boundary:
- Docker uses tar streaming with normalized permissions (dirs
0777, files0644). - Modal uses the sandbox filesystem API (
open,mkdir,ls) to stream bytes. - Multipass, QEMU (ssh/scp), and Local delegate to their native tools.
Use paths that make sense for the target: container paths for Docker/Modal, remote
VM paths for QEMU/Multipass, and normal paths for Local. Destinations are created as
needed (like mkdir -p) on both sides.
# base install
pip install hazmat
# pull in provider-specific extras
pip install "hazmat[docker]" # requires Docker Python SDK and a running daemon
pip install "hazmat[qemu]" # no extra Python deps; still needs qemu-system in PATH
pip install "hazmat[multipass]" # no extra Python deps; still needs multipass in PATH
pip install "hazmat[modal]" # requires Modal Python SDK and an API token configureduv run -m hazmat.cli --helpuv run -m hazmat.cli local -- uname -auv run -m hazmat.cli multipass --name hazmat-dev -- uname -aThe Docker provider starts a detached container from the image you specify and
runs commands inside it using docker exec. The container is removed when the
command finishes (or when the CLI exits if you run multiple commands).
uv run -m hazmat.cli docker \
--image ubuntu:24.04 \
--network-mode none \
--env BASE=1 \
--exec-env FOO=bar \
-- uname -aAdditional knobs:
-
--pull/--no-pullpulls the image before creating the sandbox. -
--mount(bind) and--volume(named) mount host paths/volumes. -
--network-mode,--nano-cpusconfigure container networking/CPU quota. -
--envsets container environment;--exec-envsets exec-time environment. -
--run-extra/--exec-extrapass raw kwargs through todocker run/docker exec(escape hatch; values parsed as JSON when possible). -
--nameforces a specific container name instead of a random one. -
--workdirsets the working directory fordocker exec. -
Using Colima? Point hazmat (and the Docker SDK) at the Colima socket first:
export DOCKER_HOST="unix://$HOME/.colima/docker.sock"
The Modal provider spins up a Modal
sandbox and runs commands inside it
using modal.Sandbox.exec. You can start from a registry image (--image) or
fall back to Modal's debian_slim() base, optionally mounting local
directories.
uv run -m hazmat.cli modal \
--image docker.io/library/python:3.12 \
--app my-app \
--mount "$PWD:/app" \
--env BASE=1 \
--exec-env FOO=bar \
--workdir /app \
-- ls -laAny mounts are added via modal.Image.add_local_dir, and per-command env vars
are merged with the defaults you configure at sandbox creation. --app is
required and is resolved with modal.App.lookup; pass --no-create-app to avoid
creating missing apps during lookup.
The QEMU provider boots a VM from a disk image that you supply (for example an image produced by Packer) and runs commands inside it over SSH. The image must contain a user that can log in using the private key you pass to hazmat (or using your SSH agent).
uv run -m hazmat.cli qemu \
--image ./packer-output/hazmat.qcow2 \
--ssh-user hazmat \
--ssh-key ~/.ssh/hazmat_id_ed25519 \
--memory-mb 4096 \
--cpus 4 \
-- uname -aAdditional knobs:
--binary,--accel, and--extra-argallow you to tweak the QEMU invocation (e.g., to enablekvm,hvf, or to attach additional devices).--boot-timeoutcontrols how long hazmat waits for SSH to become available before bailing out, while--probe-intervalsets how often to check.--no-snapshotdisables QEMU’s-snapshotflag if you need persistent writes (hazmat defaults to ephemeral writes to avoid mutating the input image).--ssh-portpins the forwarded host port;--ssh-optionappends raw tokens to the ssh invocation.