Skip to content
Foxx Cyberfoxxcyber/docs

Deployment Quickstart

Install Bedrock RMF on your own server with Docker or Podman Compose — one command, no secrets to invent, then put a reverse proxy in front of it.

Bedrock RMF runs as a small Compose stack on a single server: the application, PostgreSQL 18, MinIO for evidence storage, and a one-shot bootstrap job that prepares the database. This page takes a fresh Linux box to a working, non-default install and then covers the four things that separate a pilot from production: a real hostname, TLS, backups and upgrades.

No .env file and no secrets to invent are needed for the first run. The bootstrap generates the authentication secret and the admin password itself.

What you need

MinimumRecommended
OSAny 64-bit Linux with Docker Engine 24+ or Podman 4.9+ and a Compose plugin (docker compose / podman compose)Rocky / RHEL 9+, Ubuntu 22.04+
CPU / RAM2 vCPU, 4 GB4 vCPU, 8 GB
Disk20 GB50 GB+ on a volume you back up — evidence lives here
NetworkOutbound HTTPS once, to pull the images (or an air-gapped image copy, below)A DNS name for the app and one for storage

Everything is x86-64; the images are node:22-alpine-based.

Podman users

Every command below works with podman compose in place of docker compose. Rootless Podman is fine; the containers already run as an unprivileged user.

1. Get the Compose file

You do not need the source tree — only docker-compose.yml. On the server:

mkdir -p /opt/bedrock-rmf && cd /opt/bedrock-rmf
curl -fsSLO https://gitlab.com/foxxcyber-oss/bedrock-rmf/-/raw/main/docker-compose.yml

The file pulls two published, CI-scanned images from the public registry — the runtime server registry.gitlab.com/foxxcyber-oss/bedrock-rmf and the bootstrap job …/bedrock-rmf/bootstrap — pinned to the current release. Pin a different version with BEDROCK_VERSION=X.Y.Z in front of any command below.

2. Start it

docker compose up -d

First boot takes a minute or two. The bootstrap service waits for PostgreSQL, applies the schema, seeds the NIST SP 800-53 Rev 5 catalog (1,190 controls with 3,298 CCI mappings), the role and permission matrix, the baseline templates and the cloud service catalog, generates the auth secret, creates the initial admin, and exits. The app container only starts once bootstrap has completed successfully.

Watch it:

docker compose logs -f bootstrap    # ends with "[bootstrap] ready."
docker compose ps                   # app should be "healthy"

3. Sign in

Read the generated admin password — it is written once, to a volume only the stack can see:

docker compose run --rm bootstrap cat /secrets/initial_admin_password

Open http://SERVER:3000 and sign in as admin@foxxcyber.com (change the address with ADMIN_EMAIL=you@example.com before the first up; it is only used to create the account). Change the password immediately under Settings, and enrol two-factor authentication. Then follow First Login.

Sign in by the same address you started with

Sessions are bound to the origin the browser uses. If you started with the defaults and browse to http://10.0.0.5:3000, that works; browsing to a different hostname without setting BEDROCK_URL (next step) shows the login page but never keeps you signed in.

4. Give it a real address

For anything beyond a laptop test, run the stack behind a reverse proxy with TLS and tell Bedrock RMF the two public addresses:

BEDROCK_URL=https://rmf.example.com \
BEDROCK_S3_URL=https://rmf-files.example.com \
docker compose up -d

Two addresses, because uploads and downloads go straight from the browser to object storage with presigned URLs, and the hostname is part of the signature. Route both names to the server: the app on port 3000, storage on port 9000. The proxy must pass the original Host header through unchanged (Caddy and nginx do by default; a rewritten Host produces SignatureDoesNotMatch on every upload).

A complete Caddy configuration, automatic certificates included:

rmf.example.com {
    reverse_proxy 127.0.0.1:3000
}
rmf-files.example.com {
    reverse_proxy 127.0.0.1:9000
}

Put the same two values in a .env file next to docker-compose.yml so every future docker compose up -d remembers them:

cat > .env <<'EOF'
BEDROCK_URL=https://rmf.example.com
BEDROCK_S3_URL=https://rmf-files.example.com
POSTGRES_PASSWORD=change-me-to-something-long
S3_SECRET_KEY=change-me-too
EOF
docker compose up -d

POSTGRES_PASSWORD and S3_SECRET_KEY have working defaults for a single-box install (PostgreSQL is never published to the host, and the MinIO console is bound to loopback), but set them before the first boot on any server another machine can reach.

All settings

VariableDefaultPurpose
BEDROCK_VERSIONcurrent releaseImage tag to run (1.0.0, 1.0, 1, latest)
BEDROCK_URLhttp://localhost:3000Public origin of the app — must match what the browser uses
BEDROCK_S3_URLhttp://localhost:$MINIO_PORTPublic origin of object storage — same rule
BEDROCK_PORT3000Host port the app is published on
MINIO_PORT9000Host port storage is published on
MINIO_CONSOLE_PORT9001MinIO console (loopback only)
POSTGRES_PASSWORDbedrockDatabase password (internal network only)
S3_ACCESS_KEY / S3_SECRET_KEYbedrock / bedrock-minioMinIO credentials
ADMIN_EMAILadmin@foxxcyber.comInitial admin account (first boot only)

Using AWS S3 or another external store instead of MinIO is supported by the application (S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE); see .env.example in the repository for the full list.

Backups

Three named volumes hold everything:

VolumeContentsHow to back up
postgres-datathe database — packages, controls, POA&Ms, users, audit logdocker compose exec postgres pg_dump -U bedrock bedrock_rmf > rmf-$(date +%F).sql
minio-dataevidence files, artifacts, diagramssnapshot the volume, or mirror the bucket with mc mirror
secretsthe generated auth secret and initial admin passwordcopy the volume once and keep it somewhere safe

Losing the secrets volume logs everyone out — permanently

auth_secret signs every session and every enrolled TOTP device. If it is lost, every user must sign in again and re-enrol two-factor. Back it up the day you install.

Upgrading

Releases are semantic versions; the changelog is in the repository and on the Releases page here. To upgrade:

cd /opt/bedrock-rmf
curl -fsSLO https://gitlab.com/foxxcyber-oss/bedrock-rmf/-/raw/vX.Y.Z/docker-compose.yml
docker compose pull
docker compose up -d

The bootstrap job runs again on every up, applies only the migrations that are new, and leaves existing data and the auth secret untouched. Take a pg_dump first; migrations are forward-only.

Air-gapped installs

Pull the images on a connected machine, carry them across, load them, and run the same Compose file:

# connected side
docker pull registry.gitlab.com/foxxcyber-oss/bedrock-rmf:1.0.0
docker pull registry.gitlab.com/foxxcyber-oss/bedrock-rmf/bootstrap:1.0.0
docker pull postgres:18-alpine && docker pull minio/minio:latest && docker pull minio/mc:latest
docker save -o bedrock-rmf-1.0.0.tar \
  registry.gitlab.com/foxxcyber-oss/bedrock-rmf:1.0.0 \
  registry.gitlab.com/foxxcyber-oss/bedrock-rmf/bootstrap:1.0.0 \
  postgres:18-alpine minio/minio:latest minio/mc:latest
 
# isolated side
docker load -i bedrock-rmf-1.0.0.tar
BEDROCK_VERSION=1.0.0 docker compose up -d

Building from source instead

The repository includes an override that builds both images from the checked out tree, for development or for organizations that require building their own artifacts:

git clone https://gitlab.com/foxxcyber-oss/bedrock-rmf.git && cd bedrock-rmf
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build

Troubleshooting

  • app never becomes healthy and logs "waiting for bootstrap" — the bootstrap job failed. docker compose logs bootstrap; the usual cause is PostgreSQL still initialising on a slow disk. docker compose up -d again is safe.
  • Login succeeds but immediately returns to the login page — BEDROCK_URL does not match the address in the browser (scheme, host and port must all match).
  • Uploads fail with SignatureDoesNotMatch — the proxy in front of storage is rewriting the Host header, or BEDROCK_S3_URL is not the address the browser can reach.
  • Health check — GET /login returns 200 from a healthy instance; the container's built-in health check uses the same request.

Last updated August 27, 2026