BountyHub Docs
Custom Runner Images
Build your own runner image from the official BountyHub image to preinstall tools.
#Custom runner images
The official runner image is deliberately minimal. It ships the runner and its helper binaries and nothing else, so you can build on top of it instead of maintaining a fork of the runner whenever you need extra tooling.
Reasons to build your own image:
- Preinstall tools. Workflows invoke programs by name from
PATH. Tools baked into the image are available immediately instead of being downloaded on every run. - Keep private tooling private. Proprietary scanners can live in your image and never leave your infrastructure.
- Control versions. Pin the exact scanner/runtime versions your workflows
were tested against instead of installing
latestat run time.
#What the official image contains
| Path | Purpose |
|---|---|
/usr/local/bin/runner-worker-local | Runner controller. Also the image entrypoint. |
/usr/local/bin/bh | BountyHub CLI. Workflow steps use bh ask to reach the operator. |
/usr/local/bin/cookielab | Native cookielab CLI used by the cookielab BHW module. |
/opt/bountyhub/dsh | Pinned DeepSeek Harness payload (BH_DSH_BIN=/opt/bountyhub/dsh/bin/dsh). |
The runtime base is debian:bookworm-slim with ca-certificates and
libstdc++6 installed. The image runs as the non-root runner user (uid
1000) and has no writable home directory by default; the runner stores its
config and workdir under $HOME/.bountyhub/runners/<name>.
The runner-worker-local and runner-worker images include the dsh payload.
The runner-worker-serverless image contains the same layout minus bh and
dsh (it only starts job containers), and the runner-worker job image ships
runner-worker, cookielab, and dsh.
#Option 1: extend the official image
Derive from the published image and add your tools. The image sets
USER runner, so switch to root for package installation and back to runner
afterwards.
#Option 2: copy the binaries into your own base
If you need a different distribution, or an image where you control every layer, copy the binaries out of the official image. This is the same approach the official Dockerfiles use.
The binaries are dynamically linked, so use a base with at least the glibc
version of debian:bookworm (2.36). debian:bookworm-slim and ubuntu:24.04
both work; older bases may fail at startup.
#Build the image
Pin a specific image tag instead of latest when you want reproducible builds,
and rebuild when you upgrade the runner.
#Register and run in a container
The runner writes its config to $HOME/.bountyhub, so mount a volume over the
home directory. A named volume inherits the ownership of the directory in the
image, which keeps it writable for the non-root user.
Then run the controller:
If you bind-mount a host directory instead of using a named volume, make sure
uid 1000 can write to it, or pass --user "$(id -u):$(id -g)".
#Upgrading
Treat the image as the deployment unit: rebuild it from a newer base tag when a
new runner release is available. The upgrade subcommand replaces the running
executable in place, so it cannot update the root-owned binary in
/usr/local/bin while the container runs as runner.
#Security notes
- Never bake a registration token or platform token into the image.
- Keep
USER runnerunless a tool genuinely requires root; prefer installing tools to a writable path over running the whole runner as root. - The runner can execute arbitrary commands by design. Run one runner per container and isolate it the same way you would isolate any other scanning host.