Skip to content
GitHub
documentation

Install

Self-host Bloom with Docker Compose, Cloudron, or Platinity / K3s, then back it up and upgrade it.

Bloom is EmbedLabs’ product lifecycle management app. It runs from ghcr.io/mbedlabs/bloom with PostgreSQL. Use this Docker Compose stack:

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

The compose file exposes Bloom on host port 8000. To use another port, change the left side of 8000:8080 and use the same port in APP_BASE_URL and BLOOM_APP_URL.

Generate independent secrets, one per value, for example with openssl rand:

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

Then write the .env:

# .env
BLOOM_ENV=production
# stable follows tested non-prerelease version tags, independently of GitHub Releases.
BLOOM_VERSION=stable
POSTGRES_USER=bloom
POSTGRES_PASSWORD=replace-with-a-strong-database-password
POSTGRES_DB=bloom
SECRET_KEY=replace-with-a-strong-random-secret
ADMIN_EMAIL=you@your-company.com
ADMIN_PASSWORD=replace-with-a-strong-admin-password
ADMIN_FULL_NAME=Bloom Admin
AUTO_SEED_ADMIN=false
# Your public Bloom URL, used in the links Bloom emails (invites, verification, resets)
APP_BASE_URL=https://bloom.example.com

Set at least POSTGRES_PASSWORD, SECRET_KEY, ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_FULL_NAME, APP_BASE_URL and BLOOM_APP_URL. BLOOM_APP_URL is covered in Connecting to Bud. In production, Bloom rejects a missing or weak signing key and requires an administrator password of at least 16 characters.

Keep RUN_STARTUP_DATA_REPAIR=false. The container applies database migrations with Alembic before starting the application.

Optional features use independent secrets:

  • Set SERVICE_TOKEN_PEPPER to a random value of at least 32 characters when Bloom will issue scoped service credentials for Bud. See Connecting to Bud.
  • Set INTEGRATION_ENCRYPTION_KEY to a Fernet key when GitHub, GitLab, or Jira synchronization will store credentials. See Issue tracker sync.

Bloom starts without these optional secrets. Only the corresponding integration remains unavailable. Every other setting is in the configuration reference.

# Bloom runs its own migrations on start (see the compose command)
docker compose pull
docker compose up -d
curl http://localhost:8000/api/ready

Follow startup and migration logs:

docker compose logs -f bloom

Open http://localhost:8000/setup to create the first administrator. As with Bud, production requires a real ADMIN_EMAIL and a password of at least 16 characters even when AUTO_SEED_ADMIN=false; those values do not create an account with seeding disabled. Keep the database and attachment volumes when upgrading.

Bloom answers three health endpoints:

Endpoint What it checks
/api/health Liveness. The web process responds. It does not query PostgreSQL.
/api/ready Readiness. Runs SELECT 1 against PostgreSQL and returns 200 only when the database is reachable, 503 otherwise. The image’s own health check probes it, and it is the right target for load balancer and orchestrator readiness checks.
/api/version Reports the running version.
  • Image: ghcr.io/mbedlabs/bloom, listed as Bloom on GitHub Packages
  • Platforms: linux/amd64, linux/arm64
  • Container port: 8080
  • Default host port: 8000

Set BLOOM_VERSION in .env:

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

Pin a complete version such as 1.2.0 for production.

Cloudron uses the same Bloom 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’s normal entrypoint:

  • maps Cloudron’s PostgreSQL and optional sendmail variables to Bloom;
  • runs database migrations before starting the service;
  • stores attachments and generated application secrets under /app/data;
  • disables automatic admin seeding, so the first administrator is created at /setup;
  • exposes Bud through BUD_APP_URL when that variable is configured.

Bloom generates and persists its own application secrets. Before you install, have ready:

  • the Cloudron dashboard domain and a Cloudron login or API token;
  • the Bloom app location (its domain);
  • an immutable image tag or digest;
  • the Bud app domain, for the link between the two apps.

Install the manifest with the Bloom container image:

export CLOUDRON_SERVER='<cloudron-dashboard-domain>'
export BLOOM_LOCATION='<bloom-app-location>'
export IMAGE_TAG='<immutable-version-or-sha>'
cloudron login "$CLOUDRON_SERVER"
cloudron install --location "$BLOOM_LOCATION" \
--image "ghcr.io/mbedlabs/bloom:$IMAGE_TAG"

Set Bloom’s link to Bud once both locations are known:

cloudron env set --app "$BLOOM_LOCATION" \
BUD_APP_URL="https://bud.example.com"

The manifest declares the PostgreSQL and local storage addons and an optional Email (sendmail) addon, uses /api/ready as its health check, sets a 1 GiB memory limit and needs Cloudron 10.0.0 or later.

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, so nobody else can claim the instance.

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

Bloom has two durable stores:

  • bloom-postgres-data for PostgreSQL records
  • bloom-attachments for document evidence under /app/attachments

BLOOM_ATTACHMENT_DIR identifies the attachment directory. The Compose service sets it to /app/attachments and mounts bloom-attachments there, so replacing or upgrading the application container does not erase uploaded evidence. A non-Compose deployment must mount its own durable storage at the configured path.

Back up PostgreSQL and the attachment volume together, at the same point in time, so attachment rows and files stay consistent. Preserve SECRET_KEY, SERVICE_TOKEN_PEPPER, and INTEGRATION_ENCRYPTION_KEY with the deployment configuration so saved credentials remain usable.

Nightly backup (adjust connection details to your deployment):

# database
pg_dump --format=custom --file="bloom-$(date +%F).dump" "$DATABASE_URL"
# document evidence; use your named-volume or host-volume backup tooling
tar czf "bloom-attachments-$(date +%F).tgz" -C /path/to/attachments .

Restore:

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

After a restore, check /api/ready, then sign in and confirm that projects, requirements, traceability views, and a sample attachment download all work. Test restoration on a separate installation before you depend on it.

Keep SECRET_KEY, SERVICE_TOKEN_PEPPER, INTEGRATION_ENCRYPTION_KEY, and the SMTP credentials in your secret store:

  • a different SECRET_KEY invalidates sessions and pending invite and reset links;
  • a different SERVICE_TOKEN_PEPPER invalidates the Bud result-sync credentials;
  • a different INTEGRATION_ENCRYPTION_KEY makes configured tracker secrets unreadable until they are rotated.

Your recovery point equals your backup cadence: nightly dumps mean up to 24 hours of loss. Copy backups off the host.

Pull the chosen image, retain the volumes, and let the Compose startup command apply migrations before checking /api/ready.

  1. Read the release notes.

  2. Back up bloom-postgres-data, bloom-attachments, and .env.

  3. Set the target BLOOM_VERSION.

  4. Pull and restart:

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

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