Private Beta
.md

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 latest at run time.

#What the official image contains

PathPurpose
/usr/local/bin/runner-worker-localRunner controller. Also the image entrypoint.
/usr/local/bin/bhBountyHub CLI. Workflow steps use bh ask to reach the operator.
/usr/local/bin/cookielabNative cookielab CLI used by the cookielab BHW module.
/opt/bountyhub/dshPinned 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.

dockerfile

#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.

dockerfile

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

bash

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.

bash

Then run the controller:

bash

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 runner unless 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.