alpha — v0.1.0

bulkers

The Rust rewrite of bulker — faster, cleaner, zero Python dependencies.

Why Rust?

Zero dependencies

Single static binary. No Python, no pip, no virtualenvs. One curl command and you're done. Works on any Linux or macOS machine, no runtime required.

🚀

Instant startup

Native binary launches in milliseconds. No interpreter overhead on every command invocation. Shell activation is a real shell function — no eval tricks on every use.

📦

Drop-in replacement

Same crate manifests, same registries, same workflows. Your existing crates just work. Migrate by uninstalling the Python version and installing bulkers.

bulkers crate list
$ bulkers crate list Crate root: /home/user/.local/share/bulker/crates databio/bedtools 2.31.1 databio/pepatac 1.0.14-dev (+2 more) databio/pipestat 0.14.2 bulker/alpine default bulker/demo default bulker/samtools 1.19.2 (+1 more)

Clean, grouped list output

The new bulkers crate list shows installed crates grouped by namespace, with the latest version highlighted and a (+N more) hint when multiple versions are installed. A shared crate root path is shown in the header, keeping paths out of the main listing. Use --versions to show all installed tags, or --simple for scriptable output.

shell activation
# One-liner install adds the shell function automatically $ curl -sL https://raw.githubusercontent.com/databio/bulkers/master/install.sh | bash Installed bulkers to ~/.local/bin/bulkers Added shell function to ~/.bashrc # Then in a new shell: $ bulkers activate databio/pepatac Activated databio/pepatac:1.0.14-dev (databio/pepatac) $ samtools --version samtools 1.19.2 (databio/pepatac) $ bulkers deactivate $

Natural shell activation

The install script adds a shell function to your ~/.bashrc or ~/.zshrc automatically. After that, bulkers activate and bulkers deactivate just work — no eval "$(...)" on every use. Your prompt updates to show the active crate, and deactivate cleanly restores the original PATH.

For scripts, CI, and AI agents, bulkers exec crate -- cmd is a plain binary with no shell function required.

dynamic activation
# No install step needed — just activate $ bulkers activate databio/pepatac Fetching databio/pepatac from registry... Activated databio/pepatac:1.0.14-dev # Second time is instant — already cached $ bulkers activate databio/pepatac Activated databio/pepatac:1.0.14-dev (cached) # Optional: pre-cache without activating $ bulkers crate install databio/pepatac Cached databio/pepatac:1.0.14-dev # Clean up a cached crate $ bulkers crate clean databio/pepatac Removed databio/pepatac:1.0.14-dev from cache

Dynamic activation — just activate and go

bulkers activate databio/pepatac now fetches the manifest from the registry automatically if it isn't already cached. There's no separate install step. The lifecycle is simple: init → activate → deactivate.

Once a crate is cached, subsequent activations are instant. Use bulkers crate install only if you want to pre-cache a crate in advance (e.g., on a build step or with --build to pull images ahead of time). Use bulkers crate clean to remove a cached crate.

bulkers --help
$ bulkers --help Multi-container environment manager Usage: bulkers <COMMAND> Commands: init Initialize bulkers config activate Put crate commands on PATH (shell function) deactivate Restore original PATH (shell function) exec Run a command in a crate environment crate Manage crates (install, list, inspect, clean) config Manage configuration (show, get, set) init-shell Print shell function for ~/.bashrc or ~/.zshrc mock Record/replay container outputs for testing $ bulkers crate --help Commands: install Pre-cache from registry, URL, or local file (optional) clean Remove a cached crate list List cached crates inspect Show commands in a crate $ bulkers mock --help Commands: record Record real container outputs to mock fixtures run Replay mock outputs (no Docker required)

Organized command hierarchy

Commands are grouped into logical subgroups: daily use commands (activate, exec) at the top level, optional cache management under bulkers crate, and configuration under bulkers config. No more flat list of commands to memorize.

crate uninstall and crate update are gone. Use crate clean to remove a cached crate, and simply re-activate to pull a fresh copy. Mock subcommands are now cleanly grouped as mock record and mock run.

Shell completions are generated via bulkers init-shell, giving you tab-complete for commands, subcommands, and installed crate names.

bulkers config show
$ bulkers config show crate_dir: /home/user/.local/share/bulker/crates default_ns: bulker container: apptainer $ bulkers config set container=apptainer Set container = apptainer # bulkers uses apptainer or singularity — whichever is present $ bulkers activate bulker/samtools Activated bulker/samtools:1.19.2 using: apptainer exec ...

First-class Apptainer support

Apptainer (the successor to Singularity) is now a first-class container runtime alongside Docker. Set container = apptainer in your config and crates run natively with apptainer exec. No shims, no hacks. Both apptainer and singularity are supported; bulkers uses whichever is on your PATH.

This matters on HPC clusters where Docker is unavailable and Apptainer is the standard.

runtime imports
# my-pipeline.yaml imports shared crates manifest: name: my-pipeline imports: - bulker/samtools - bulker/bedtools commands: - command: my-tool docker_image: my-org/my-tool:latest # Imports are resolved at activate time — always up to date $ bulkers activate my-org/my-pipeline + bulker/samtools:1.19.2 + bulker/bedtools:2.31.1 Activated my-org/my-pipeline # Or activate from a local manifest file directly $ bulkers activate ./my-pipeline.yaml Activated my-pipeline (local)

Runtime imports & local manifests

Cratefiles can import other crates. Imports are resolved at activate time, not install time. Update a shared crate once, and all cratefiles that import it automatically pick up the change. No rebuilding, no reinstalling — just activate and go.

You can also activate directly from a local YAML file: bulkers activate ./my-pipeline.yaml. No need to publish to a registry or run an install step first — point at a file and you're in.

This makes it practical to maintain a small library of well-tested tool crates and compose them into pipeline environments without duplication.

mock mode
# Record real container outputs to fixtures $ bulkers mock record bulker/samtools -- samtools --version Recorded: samtools --version Wrote mock to .bulkers-mock/bulker-samtools/ # Replay in CI — no Docker required $ bulkers mock run bulker/samtools -- samtools --version samtools 1.19.2 (mock) # Tests run in seconds, not minutes

Mock mode for testing

Record real container outputs with bulkers mock record, then replay them with bulkers mock run. Tests that depend on containerized tools run in seconds without Docker. Mock outputs are plain files you can commit to your test fixtures.

This makes it practical to write fast, deterministic tests for pipelines that use bulkers-managed tools — without pulling images in CI.

shimlinks
# Commands are symlinks to the bulkers binary $ ls -la ~/.local/share/bulker/shims/ samtools -> /home/user/.local/bin/bulkers bedtools -> /home/user/.local/bin/bulkers bowtie2 -> /home/user/.local/bin/bulkers # argv[0] dispatch — bulkers knows which tool was called $ samtools --version samtools 1.19.2 # Change config, changes take effect immediately — no reload $ bulkers config set container=apptainer Set container = apptainer $ samtools --version # now runs via apptainer, no restart needed samtools 1.19.2

Shimlinks — no reload needed

When a crate is activated, each tool command is a symlink pointing to the bulkers binary itself. When you call samtools, bulkers inspects argv[0] to know which tool was requested and dispatches accordingly.

This means config changes take effect immediately — no shell reload, no re-activation. Update your container runtime, change a mount, toggle a flag: the next invocation picks it up automatically.

Install

Quick install

curl -sL https://raw.githubusercontent.com/databio/bulkers/master/install.sh | bash

Downloads the binary and adds the shell function to your ~/.bashrc or ~/.zshrc. Restart your shell or source ~/.bashrc after installing.

Manual install

# Linux x86_64 curl -sL https://github.com/databio/bulkers/releases/latest/download/bulkers-Linux-musl-x86_64.tar.gz | tar xz && mv bulkers ~/.local/bin/ # macOS Apple Silicon curl -sL https://github.com/databio/bulkers/releases/latest/download/bulkers-macOS-arm64.tar.gz | tar xz && mv bulkers ~/.local/bin/ # macOS Intel curl -sL https://github.com/databio/bulkers/releases/latest/download/bulkers-macOS-x86_64.tar.gz | tar xz && mv bulkers ~/.local/bin/ # Then add the shell function manually: eval "$(bulkers init-shell bash)" # add to ~/.bashrc eval "$(bulkers init-shell zsh)" # add to ~/.zshrc

Build from source

git clone https://github.com/databio/bulkers cd bulkers cargo build --release ./install.sh

Migrating from Python bulker?

# Remove the Python version pip uninstall bulker # Install bulkers (your existing crates and config are compatible) curl -sL https://raw.githubusercontent.com/databio/bulkers/master/install.sh | bash

Quick start

end-to-end workflow
# Initialize config (first time only) $ bulkers init Created ~/.config/bulker/bulker.yaml # Activate — fetches from registry automatically if not cached $ bulkers activate databio/pepatac Fetching databio/pepatac from registry... Activated databio/pepatac:1.0.14-dev # You're in! Use tools normally (databio/pepatac) $ samtools --version samtools 1.19.2 (databio/pepatac) $ trim_galore --help Trim Galore version 0.6.10 # Done (databio/pepatac) $ bulkers deactivate $ # Or use exec for one-off commands (no shell function needed) $ bulkers exec databio/pepatac -- samtools view -h input.bam

bulkers is in alpha. It's a full rewrite — not a port — with architectural improvements throughout. The Python bulker continues to work; we recommend trying bulkers alongside it.

Core features are stable. Some edge cases and less-common container runtimes are still being worked out.

Report issues on GitHub — feedback during alpha directly shapes the API.