Skip to content
GitHub
documentation

Install

Self-host Bud with Docker Compose, Cloudron, or Platinity / K3s, and create the first administrator.

Bud TMP runs from one container image serving the UI and the /api surface, plus PostgreSQL. Use this Docker Compose stack:

To install the bud-runner and budtestlibrary packages on a test host, see Getting started.

  • Docker Engine with Docker Compose v2
# docker-compose.yml
services:
bud:
image: ghcr.io/mbedlabs/bud:${BUD_VERSION:-stable}
env_file:
- .env
environment:
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
depends_on:
postgres:
condition: service_healthy
ports:
- "8001:8080"
# Bud migrates on start, then launches the app
command: ["/bin/sh", "-c", "gosu appuser alembic upgrade head && exec /usr/local/bin/start-product"]
volumes:
- bud-uploads:/app/uploads
postgres:
image: postgres:16
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- bud-postgres-data:/var/lib/postgresql/data
volumes:
bud-postgres-data:
bud-uploads:

The compose file exposes Bud on host port 8001. To use another port, change the left side of 8001:8080 and use the same port in APP_BASE_URL and BUD_APP_URL.

Generate an independent random value for each secret, for example with openssl rand:

openssl rand -hex 24
openssl rand -hex 32
openssl rand -hex 24

Then write the .env next to the compose file and replace every replace-with-... value:

# .env
BUD_ENV=production
# stable follows tested non-prerelease version tags, independently of GitHub Releases.
BUD_VERSION=stable
DB_USER=bud
DB_PASSWORD=replace-with-a-strong-database-password
DB_NAME=buddb
SECRET_KEY=replace-with-a-strong-random-secret
# Required production configuration; AUTO_SEED_ADMIN=false leaves account creation to /setup
ADMIN_EMAIL=you@example.com
ADMIN_PASSWORD=replace-with-a-strong-admin-password
ADMIN_FULL_NAME=Bud Admin
AUTO_SEED_ADMIN=false
# Your public Bud URL, used in the links Bud emails (invites, verification, resets)
APP_BASE_URL=https://bud.example.com
BUD_APP_URL=https://bud.example.com

In production, startup refuses the admin@example.com placeholder: set a real admin email and a strong password of at least 16 characters. Bud also rejects placeholder secrets. With AUTO_SEED_ADMIN=false, these values satisfy startup validation but do not create an account. Create the first administrator at /setup.

Keep RUN_STARTUP_DATA_REPAIR=false. Database schema changes are applied only through Alembic.

The repository’s .env.example carries the full key list, including SMTP. Every key is described in the Configuration reference.

docker compose up -d # the container runs alembic upgrade head before serving
curl http://localhost:8001/api/ready

No separate migration step is needed. Follow the migration and startup logs with:

docker compose logs -f bud

Bud answers on three status endpoints:

Endpoint What it checks
/api/health Liveness. The API process is up. It does not touch PostgreSQL.
/api/ready Readiness. Runs SELECT 1 against PostgreSQL and returns 200 only when the database is reachable, 503 otherwise. The container health check probes it. Use it for load balancer and orchestrator readiness checks.
/api/version The running version.

Open http://localhost:8001/setup to create the first administrator. Setup closes once a user exists. Open Test Stations and create a separate enrolment key for each station, then follow the runner registration steps.

  • Image: ghcr.io/mbedlabs/bud
  • Package: Bud on GitHub Packages
  • Platforms: linux/amd64, linux/arm64
  • Container port: 8080
  • Default host port: 8001

Set BUD_VERSION in .env to one of these tags:

Tag Moves? Use for
1.2.0 (full version) Immutable Production: pin this
1.2, 1 Moves with stable releases Tracking a minor or major line
stable Moves to the newest stable release Low-maintenance production
sha-<commit> Immutable Exact reproducibility and debugging
latest Rolling build Never in production

Pin a complete version such as 1.2.0 for production.

Bud has two durable stores:

  • bud-postgres-data for PostgreSQL.
  • bud-uploads for uploaded artifacts under /app/uploads.

BUD_UPLOAD_DIR (or the unprefixed UPLOAD_DIR alias) identifies the artifact directory. The Compose service mounts the bud-uploads volume there, so replacing the application container does not delete runner evidence. A deployment without Compose must mount durable storage at its configured path.

Back up both stores and the deployment secrets together. Adjust the connection details to your deployment:

# database
pg_dump --format=custom --file="bud-$(date +%F).dump" "$DATABASE_URL"
# uploaded artifacts
tar czf "bud-uploads-$(date +%F).tgz" -C /path/to/uploads .

Restore:

pg_restore --clean --if-exists --no-owner --dbname "$DATABASE_URL" bud-YYYY-MM-DD.dump
tar xzf bud-uploads-YYYY-MM-DD.tgz -C /path/to/uploads

After a restore, check /api/ready, then sign in and confirm recent runs and artifacts are visible. Test restoration on a separate installation before you depend on it.

Keep SECRET_KEY and BUD_INTEGRATION_ENCRYPTION_KEY in your secret store. A different SECRET_KEY invalidates sessions and runner tokens, so runners must re-register. A different BUD_INTEGRATION_ENCRYPTION_KEY makes the stored Bloom credential unreadable, so it must be rotated.

  1. Read the release notes.

  2. Back up bud-postgres-data, bud-uploads, and .env.

  3. Set the target BUD_VERSION.

  4. Pull and restart. The container runs alembic upgrade head before serving traffic.

    docker compose pull
    docker compose up -d
  5. Verify /api/version and /api/ready.

Database downgrades are not supported. To roll back a release that changed the database schema, restore the pre-upgrade backups and start the previous image tag. Never run a newer schema against an older application version.

Cloudron uses the same Bud container image. Follow the repository’s Cloudron installation instructions for the manifest and install command. Cloudron provides the database, runs migrations, and keeps persistent files under /app/data. Create the first administrator at /setup after installation.

On Cloudron, the image:

  • maps Cloudron’s PostgreSQL and optional mail variables to Bud;
  • runs database migrations before starting the service;
  • stores uploads and generated application secrets under /app/data;
  • disables automatic admin seeding, so the first administrator is created at /setup;
  • shows a link to Bloom when BLOOM_APP_URL is set.

The app uses the PostgreSQL and local storage addons, and the Email addon optionally. Its health check is /api/ready. Bud generates and persists its own application secrets.

Install from the directory that holds the manifest:

cloudron login <cloudron-dashboard-domain>
cloudron install --location <bud-app-location> \
--image ghcr.io/mbedlabs/bud:1.2.0

Set the link to Bloom once both locations are known:

cloudron env set --app <bud-app-location> \
BLOOM_APP_URL="https://bloom.example.com"

Other settings from the Configuration reference are set the same way, with cloudron env set.

Then open Bud. The instance starts with no accounts, so the first visit shows a setup screen that asks for your name, email address, and a password. It stops being reachable the moment the account exists: nobody else can claim the instance.

For a Kubernetes or K3s platform on infrastructure you control, review Platinity. Use the same Bud container image and keep PostgreSQL, uploads, and application secrets on durable storage managed by that platform.