Skip to content
Docs

Bulker Skill Reference

Bulker manages multi-container computing environments. You define a “crate” — a YAML manifest listing Docker images and the commands they provide. Activate a crate and bulker creates symlinks that dispatch each command to the right container at runtime. Commands appear on your PATH as if natively installed. No install step needed — just bulker activate and go.

Documentation links: Links below point to .md files at bulker.io (e.g., https://bulker.io/guides/shell.md). These return raw markdown that AI agents can fetch and read directly.

# One-liner install (Linux/macOS), installs binary to ~/.local/bin/bulker and adds shell function to ~/.bashrc or ~/.zshrc
curl -sL https://bulker.io/install.sh | bash
bulker --version # verify installation

No configuration step is needed. Bulker auto-detects your container engine and creates a config file on first use. To customize, see the Configuration reference.

Activate a crate (interactive shell):

bulker activate databio/pepatac
# Auto-fetches from registry if not cached locally
# You're now in a shell where samtools, bowtie2, etc. are on PATH
bulker crate inspect
samtools --version
bulker deactivate

activate auto-fetches the manifest from the registry on first use, creates symlinks for each command, and prepends them to your PATH. deactivate restores the original PATH. No separate install step needed. For a step-by-step walkthrough, see the Tutorial.

Pre-cache for offline use (optional):

bulker crate install databio/pepatac:1.0.13 # cache manifest
bulker crate install databio/pepatac:1.0.13 -b # cache manifest + pull images

Run a single command (non-interactive, for scripts/AI):

bulker exec databio/pepatac -- samtools view -h input.bam

exec is the preferred method for AI agents and scripts — it runs a single command in the crate’s environment without modifying your current shell. See also Shell Debugging for interactive container inspection.

See what’s cached:

bulker crate list # list all cached crates
bulker crate list --simple # space-separated, for scripting
bulker crate inspect databio/pepatac # show commands in this crate
bulker crate clean databio/pepatac # remove a cached crate
bulker crate clean --all # clear entire cache

For a guided walkthrough, see Write a Manifest. For the complete field reference, see Manifest Format.

manifest:
name: my-tools
commands:
- command: samtools
docker_image: quay.io/biocontainers/samtools:1.9--h91753b0_8

Each field:

  • command — the name you’ll type on the command line
  • docker_image — the full Docker image reference (registry/repo:tag)
manifest:
name: biotools
version: "1.0"
commands:
- command: samtools
docker_image: quay.io/biocontainers/samtools:1.9--h91753b0_8
- command: bedtools
docker_image: quay.io/biocontainers/bedtools:2.29.2--hc088bd4_0
- command: python
docker_image: python:3.11
docker_command: python
  • docker_command — what to run inside the container. Defaults to the command value. Use when the container command name differs from what you want the user to type.
  • docker_args — extra Docker flags. Bulker auto-detects TTY and always keeps stdin open, so -i and -t are not needed here (they are stripped if present).
manifest:
name: data-tools
commands:
- command: mytool
docker_image: myimage:latest
volumes:
- /data/shared
- /scratch

How mounting works (see Volumes and Environment Variables for details):

  • By default, $HOME and the temp directory ($TMPDIR, or /tmp if unset) are always mounted.
  • Each volume entry becomes --volume "/path:/path" — same path inside and outside the container.
  • The working directory is automatically set to $(pwd) so relative paths work.
  • User ID mapping is automatic (--user=$(id -u):$(id -g)) so files are owned by you, not root.
  • Additional system mounts are added automatically: /etc/passwd, /etc/group, /etc/shadow, /etc/sudoers.d (read-only), /tmp/.X11-unix (for GUI apps).

When you need extra volumes:

  • Data directories outside $HOME: add them to volumes:
  • Shared network mounts: add them to volumes:
  • You do NOT need to mount $HOME — it’s already mounted globally
manifest:
name: display-tools
commands:
- command: firefox
docker_image: jess/firefox
envvars:
- DISPLAY
- XAUTHORITY
no_network: false
  • envvars — allowlist of environment variable names for --strict-env mode. In normal mode, all host env vars are passed automatically. See Strict Environments.
  • DISPLAY is in the default allowlist (in bulker config).
  • no_network: false (the default) adds --network=host. See Container User and Network Settings.
manifest:
name: advanced
commands:
- command: redis-server
docker_image: redis:7
docker_command: redis-server
docker_args: "--name redis -p 6379:6379"
workdir: /data
no_user: true
no_network: false
- command: jq
docker_image: ghcr.io/jqlang/jq
docker_args: "--entrypoint jq"
docker_command: " "

Field reference:

FieldTypeDefaultDescription
commandstringrequiredExecutable name created on PATH
docker_imagestringrequiredDocker image reference
docker_commandstringcommandCommand to run in container
docker_argsstringnoneExtra docker run flags (note: -i/-t are auto-managed)
volumeslist[]Additional mount paths
envvarslist[]Env var allowlist for --strict-env mode
no_userboolfalseIf true, run as root (not current user)
no_networkboolfalseIf true, don’t add --network=host
workdirstring$(pwd)Working directory in container
apptainer_argsstringnoneExtra apptainer exec flags (Apptainer only; analogous to docker_args)
apptainer_commandstringcommandCommand to run inside the Apptainer container (Apptainer only; analogous to docker_command)
no_default_volumesboolfalseIf true, skip the config-level volumes list for this command (useful for containers that conflict with $HOME mounts)
manifest:
name: pepatac
version: "1.0.14"
imports:
- bulker/coreutils
- databio/bedstuff
host_commands:
- python3
- perl
- git
commands:
- command: samtools
docker_image: quay.io/biocontainers/samtools:1.9--h91753b0_8
  • imports — other crates whose commands are included when this crate is activated. Resolved at runtime — updating an imported crate propagates automatically. See Import Crates.
  • host_commands — commands from the host system (not containerized) that should be available in the crate. Symlinks to the host binary are created in the crate directory. See Host Commands.
  • Use host_commands for tools that don’t need containerization (python3, perl, git) or that need direct host access.
# Activate directly from a local file
bulker activate ./my-manifest.yaml
# Pre-cache a local manifest (optional)
bulker crate install ./my-manifest.yaml
# Pre-cache and pull all Docker images
bulker crate install ./my-manifest.yaml -b
# Exec with a local manifest
bulker exec ./my-manifest.yaml -- samtools --version
# Override the cached identity with --name
bulker crate install --name myorg/mytool:2.0 ./manifest.yaml
bulker activate myorg/mytool:2.0

When the argument starts with ./, /, or ends in .yaml/.yml, it is treated as a local manifest file. The manifest is cached using its name field (parsed as namespace/crate). If the name has no namespace (no /), the default_namespace from config is used. Use --name to override the cached identity. Otherwise the argument is treated as a registry shorthand (namespace/crate:tag).

The registry is a static collection of YAML files served from GitHub via hub.bulker.io. Publishing means adding your manifest file to this repository. See the Registry guide for more on configuring registries.

# 1. Write your manifest
cat > my-crate.yaml << 'EOF'
manifest:
name: my-crate
version: "1.0"
commands:
- command: mytool
docker_image: myorg/mytool:1.0
EOF
# 2. Fork or clone the hub.bulker.io repo
git clone git@github.com:databio/hub.bulker.io.git
cd hub.bulker.io
# 3. Add your manifest under your namespace
mkdir -p mynamespace
cp ../my-crate.yaml mynamespace/my-crate.yaml
# For versioned: mynamespace/my-crate_1.0.yaml
# 4. Commit and push
git add mynamespace/my-crate.yaml
git commit -m "Add mynamespace/my-crate"
git push origin main
# 5. Open a pull request to databio/hub.bulker.io

File naming convention: Always use versioned filenames: namespace/crate_name_1.0.yaml. The build system automatically creates the default tag (namespace/crate_name.yaml) as a symlink to the latest version.

After merge, the crate is available via: bulker activate mynamespace/my-crate:1.0

See the full Configuration reference for all config keys and their defaults. See also: Multi-user Environments for shared/HPC setups, Shell Completions for tab completion, and Command Dispatch for how shimlinks route commands at runtime.

Config file location lookup order:

  1. -c flag on any command
  2. $BULKERCFG environment variable
  3. ~/.config/bulker/bulker_config.yaml
  4. No file found — use built-in defaults with auto-detected engine (config is cached to ~/.config/bulker/bulker_config.yaml on first use)

Setting config values from the CLI:

bulker config show # view current config
bulker config get container_engine # get a specific value
bulker config set volumes=$HOME,/data/shared,/scratch # set a value (comma-separated lists)
bulker config add volumes=/data/new_mount # add to a list field
bulker config remove volumes=/data/old_mount # remove from a list field
# Bulker auto-detects apptainer if docker is not available.
# To force apptainer when both are installed:
bulker config set container_engine=apptainer
# Or regenerate the config with apptainer:
bulker config init -e apptainer --force

Differences from Docker:

  • Images are stored as .sif files in apptainer_image_folder.
  • $HOME is always mounted by Apptainer (not listed separately).
  • Uses -B for bind mounts instead of --volume.
  • First run pulls and converts the Docker image (cached for future runs).
  • Same manifests work for both engines — no changes needed.

To test a process that runs multiple tools, create a manifest listing the commands your tests need and keep it alongside your test script:

tests/
├── test-manifest.yaml
└── run-tests.sh
# tests/test-manifest.yaml
manifest:
name: myproject/tests
commands:
- command: samtools
docker_image: quay.io/biocontainers/samtools:1.17--hd87286a_2
- command: bedtools
docker_image: quay.io/biocontainers/bedtools:2.31.0--hf5e1c6e_2
#!/bin/bash
# tests/run-tests.sh
set -e
bulker activate ./tests/test-manifest.yaml
# ... run your tests ...
bulker deactivate

When a test fails because a command is missing, add it to the manifest — just another entry in the YAML.

Permission pattern for Bash commands. Users typically allow Bash(bulker:*) so that all bulker commands run without prompting. This means every Bash command must start with the word bulker. Violations:

  • R_PROFILE_USER=/tmp/x bulker exec ... — env var prefix breaks the match
  • export VAR=x && bulker exec ... — chained non-bulker command breaks the match
  • ls /some/path — not a bulker command at all

Instead:

  • Set environment variables inside the R/Python expression: bulker exec crate -- Rscript -e 'Sys.setenv(FOO="bar"); ...'
  • For file inspection, use the agent’s built-in tools (Read, Glob, Grep), not ls/cat/grep via Bash
  • Route ALL Bash commands through bulker exec or other bulker subcommands

Pattern: Set up a bioinformatics environment

# Run a single command with exec — auto-fetches manifest on first use
bulker exec databio/pepatac -- samtools view -h input.bam > output.sam

Pattern: Create a custom crate for a project

cat > manifest.yaml << 'EOF'
manifest:
name: my-analysis
commands:
- command: samtools
docker_image: quay.io/biocontainers/samtools:1.17--hd87286a_2
- command: bedtools
docker_image: quay.io/biocontainers/bedtools:2.31.0--hf5e1c6e_2
- command: R
docker_image: r-base:4.3
docker_command: R
host_commands:
- python3
- git
EOF
# Activate directly from local file
bulker activate ./manifest.yaml
samtools --version
bulker deactivate
# Or exec without activating
bulker exec ./manifest.yaml -- samtools --version

Pattern: Run a pipeline step

# Non-interactive, for scripts -- no shell function needed
bulker exec databio/pepatac -- trim_galore --paired R1.fastq.gz R2.fastq.gz -o trimmed/
bulker exec databio/pepatac -- bowtie2 -x /data/genomes/hg38 -1 trimmed/R1.fq.gz -2 trimmed/R2.fq.gz -S aligned.sam

Pattern: Check what’s available before running

bulker crate list
bulker crate inspect databio/pepatac
# Shows: samtools, bowtie2, trim_galore, bedtools, ...

Pattern: Multiple crates at once

# Activate multiple crates (commands from all are on PATH)
bulker activate databio/pepatac,bulker/coreutils
# Exec with multiple crates
bulker exec databio/pepatac,bulker/coreutils -- samtools --version

Pattern: Strict mode (only crate commands on PATH) — see Strict Environments

# No host commands leak into the environment
bulker activate -s databio/pepatac
bulker exec -s databio/pepatac -- samtools --version

Pattern: Strict-env mode (clean container environment) — see Strict Environments

# Only allowlisted env vars passed to containers
bulker activate --strict-env databio/pepatac
bulker exec --strict-env databio/pepatac -- samtools --version
# Combine both: strict PATH + strict env
bulker activate -s --strict-env databio/pepatac

Mock mode replays pre-recorded container outputs without Docker or Apptainer. Use it in CI or when containers are unavailable. See the Mock Mode guide for a full walkthrough.

# Record: run with real containers, capture outputs
bulker mock record databio/pepatac:1.0.13 outputs.json -- python pipeline.py sample1
# Replay: interactive shell with mock commands
bulker mock run databio/pepatac:1.0.13 outputs.json
# Replay: for scripts/CI (print exports, no shell)
eval "$(bulker mock run databio/pepatac:1.0.13 outputs.json --echo)"
python pipeline.py sample1

Deprecated commands (Python-era — do not use)

Section titled “Deprecated commands (Python-era — do not use)”

Bulker was rewritten from Python to Rust. Old commands like bulker load, bulker run, bulker init, bulker list, and bulker inspect no longer exist. The key replacements: load → crate install (but usually unnecessary — activate auto-fetches), run → exec, init → config init (auto-generates on first use). Manifests are unchanged. See the Migrating from Python guide for the full mapping.

See the full Glossary for additional terms. Also see the CLI Reference for complete command documentation and Troubleshooting for common issues.

TermDefinition
CrateA loaded collection of containerized commands, identified by namespace/name:tag
ManifestYAML file defining the commands in a crate, their Docker images, and configuration
NamespaceOrganizational prefix (e.g., databio, bulker). Like a GitHub org.
TagVersion identifier for a crate (e.g., 1.0.13, default)
RegistryHTTP endpoint serving manifest YAML files (default: hub.bulker.io)
ShimlinkA symlink to the bulker binary. When invoked (e.g., as samtools), bulker checks argv[0] and dispatches to the right container at runtime.
Strict modeActivation mode (-s) where only crate commands are on PATH — no host commands leak through. The container engine still works via its absolute engine_path.
ActivateAuto-fetch a manifest, create shimlinks, and put them on your PATH. Works with registry crates and local files.
DeactivateRestore the original PATH, removing crate commands.
ExecRun a single command in a crate’s environment without modifying your shell
Host commandsCommands from the host system (not containerized) included in a crate via host_commands in the manifest.
Mock modeRecord and replay container command outputs without a container runtime. For CI/testing.
Auto-mountAutomatic volume mounting triggered by file path arguments. When a shimlinked command receives a non-flag argument that looks like a path, bulker resolves it to an absolute path and mounts its parent directory into the container. No configuration required.
Manifest cacheThe filesystem directory (~/.config/bulker/manifests/) where fetched manifest YAML files are stored. Acts as the source of truth for installed crates; there is no separate crates map in the config file.