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.
Requirements
Section titled “Requirements”- Docker Engine with Docker Compose v2
1. Create the compose file
Section titled “1. Create the compose file”# docker-compose.ymlservices: 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.
2. Create the .env
Section titled “2. Create the .env”Generate an independent random value for each secret, for example with openssl rand:
openssl rand -hex 24openssl rand -hex 32openssl rand -hex 24Then write the .env next to the compose file and replace every replace-with-... value:
# .envBUD_ENV=production
# stable follows tested non-prerelease version tags, independently of GitHub Releases.BUD_VERSION=stable
DB_USER=budDB_PASSWORD=replace-with-a-strong-database-passwordDB_NAME=buddbSECRET_KEY=replace-with-a-strong-random-secret
# Required production configuration; AUTO_SEED_ADMIN=false leaves account creation to /setupADMIN_EMAIL=you@example.comADMIN_PASSWORD=replace-with-a-strong-admin-passwordADMIN_FULL_NAME=Bud AdminAUTO_SEED_ADMIN=false
# Your public Bud URL, used in the links Bud emails (invites, verification, resets)APP_BASE_URL=https://bud.example.comBUD_APP_URL=https://bud.example.comIn 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.
3. Migrate, start, verify
Section titled “3. Migrate, start, verify”docker compose up -d # the container runs alembic upgrade head before servingcurl http://localhost:8001/api/readyNo separate migration step is needed. Follow the migration and startup logs with:
docker compose logs -f budBud 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.
Images and versions
Section titled “Images and versions”- 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.
Data and backups
Section titled “Data and backups”Bud has two durable stores:
bud-postgres-datafor PostgreSQL.bud-uploadsfor 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:
# databasepg_dump --format=custom --file="bud-$(date +%F).dump" "$DATABASE_URL"
# uploaded artifactstar 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.dumptar xzf bud-uploads-YYYY-MM-DD.tgz -C /path/to/uploadsAfter 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.
Upgrade
Section titled “Upgrade”-
Read the release notes.
-
Back up
bud-postgres-data,bud-uploads, and.env. -
Set the target
BUD_VERSION. -
Pull and restart. The container runs
alembic upgrade headbefore serving traffic.docker compose pulldocker compose up -d -
Verify
/api/versionand/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
Section titled “Cloudron”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_URLis 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.0Set 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.
Platinity / K3s
Section titled “Platinity / K3s”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.



