Run Biofilter without installing it. One image, built from
Dockerfile and published to the GitHub Container Registry:
ghcr.io/ritchielab/biofilter
The same image serves Docker locally and Apptainer on a cluster — what changes is the flag that mounts the bundle, not the image.
The image contains no data. A bundle is bind-mounted at run time, so the same image serves any bundle — and a 20+ GB bundle of ZSTD parquet is something an image layer would not compress anyway.
The contract is two mounts:
| Mount | Mode | Holds |
|---|---|---|
/bundle |
read-only | The bundle directory, the one with manifest.json |
/workspace |
writable | Where --output writes |
BIOFILTER_BUNDLE defaults to /bundle in the image, so binding there
needs no extra environment.
From the project root:
docker build -t biofilter:latest -f docker/Dockerfile .Mount the bundle read-only, mount a directory for output, and name the bundle path as the container sees it:
docker run --rm \
-v /path/to/bundles/20260914:/bundle:ro \
-v "$(pwd)/out:/workspace" \
biofilter:latest \
report run --report-name annotate_gene --input TP53 --output /workspace/genes.csvFour things to get right:
- Mount the bundle root, the directory holding
manifest.json— not itstables/subdirectory. --outputwrites inside the container. Point it at the mounted/workspace, or the file disappears with the container.:rois worth setting. A bundle is read-only by nature and nothing in the read path writes to it, so the mount can say so.- Output ownership. The image runs as its own user, so under Docker
the files land owned by that uid. Add
--user "$(id -u):$(id -g)"to get your own. Under Apptainer this does not arise — the container runs as you.
With an env file instead:
cp docker/.env.example docker/.env # then edit
docker run --rm --env-file docker/.env \
-v /path/to/bundles/20260914:/bundle:ro \
biofilter:latest report listA platform that mounts elsewhere — WDL and CWL runners generally do —
either sets BIOFILTER_BUNDLE to its own path or passes --bundle.
The same image, converted to a .sif on pull. Nothing else changes —
--bind where Docker says -v, --env where Docker says -e:
apptainer pull bf4.sif docker://ghcr.io/ritchielab/biofilter:latest
mkdir -p ~/bf4_output
apptainer run \
--bind /project/hall_shared/datasets/biofilter/20260914:/bundle:ro \
--bind ~/bf4_output:/workspace \
bf4.sif \
report run --report-name annotate_gene --input APOE --output /workspace/apoe.csvTwo differences from Docker worth knowing:
- Output ownership is not a problem. Apptainer runs the container as the invoking user, so the image's own user is ignored and files land owned by you.
- The container filesystem is read-only. Anything written has to go to
a bind, which is what
/workspaceis for.
Often not. On a cluster where you can create a virtualenv,
pip install biofilter and pointing at the bundle works and skips the
image entirely. The container earns its place when you want the identical
environment across machines, or when cluster policy prefers it.
For the Penn LPC specifically — module tree, shared bundle location, LSF
job templates — see
notebooks/lpc__quickstart.md for
users and notebooks/lpc__deploy.md for
whoever maintains the install.
Only the ETL, bundle plan and the db commands need one. Reports do
not.
docker run --rm \
-e DATABASE_URL="postgresql+psycopg2://user:password@host:5432/biofilter_dev" \
biofilter:latest db pingA full bundle build inside a container is possible but rarely what you
want: it needs around 150 GB of working space and runs for about two days.
See Bundle Requirements.
The CLI resolves this itself; the entrypoint passes the environment through untouched. In order:
--bundleon the command line--db-urion the command lineBIOFILTER_BUNDLEDATABASE_URL, thenBIOFILTER_DB_URI.biofilter.toml—[database] bundle, then[database] db_uri
--bundle and --db-uri together is an error rather than a guess about
which you meant.
Mounting your project directory at /workspace lets the container pick up
a .biofilter.toml you already have — though inside a container, the
paths in it have to be the paths the container sees:
docker run --rm -v "$(pwd):/workspace" biofilter:latest config showdocker run --rm -it \
-v /path/to/bundles/20260914:/bundle:ro \
-e BIOFILTER_BUNDLE=/bundle \
--entrypoint /bin/bash \
biofilter:latestVia GitHub Actions, which is the supported path:
.github/workflows/docker-publish.yml builds and publishes. It triggers
on a pushed git tag (v4.3.0 publishes 4.3.0 and latest), or
manually from the Actions tab.
GHCR authenticates with the workflow's own token, so publishing needs no repository secrets and works in a fork.
Manually, if you have to:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f docker/Dockerfile \
-t ghcr.io/ritchielab/biofilter:4.3.0 \
-t ghcr.io/ritchielab/biofilter:latest \
--provenance=false --sbom=false \
--push .