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
| Minimum | Recommended | |
|---|---|---|
| OS | Any 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 / RAM | 2 vCPU, 4 GB | 4 vCPU, 8 GB |
| Disk | 20 GB | 50 GB+ on a volume you back up — evidence lives here |
| Network | Outbound 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.ymlThe 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 -dFirst 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_passwordOpen 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 -dTwo 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 -dPOSTGRES_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
| Variable | Default | Purpose |
|---|---|---|
BEDROCK_VERSION | current release | Image tag to run (1.0.0, 1.0, 1, latest) |
BEDROCK_URL | http://localhost:3000 | Public origin of the app — must match what the browser uses |
BEDROCK_S3_URL | http://localhost:$MINIO_PORT | Public origin of object storage — same rule |
BEDROCK_PORT | 3000 | Host port the app is published on |
MINIO_PORT | 9000 | Host port storage is published on |
MINIO_CONSOLE_PORT | 9001 | MinIO console (loopback only) |
POSTGRES_PASSWORD | bedrock | Database password (internal network only) |
S3_ACCESS_KEY / S3_SECRET_KEY | bedrock / bedrock-minio | MinIO credentials |
ADMIN_EMAIL | admin@foxxcyber.com | Initial 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:
| Volume | Contents | How to back up |
|---|---|---|
postgres-data | the database — packages, controls, POA&Ms, users, audit log | docker compose exec postgres pg_dump -U bedrock bedrock_rmf > rmf-$(date +%F).sql |
minio-data | evidence files, artifacts, diagrams | snapshot the volume, or mirror the bucket with mc mirror |
secrets | the generated auth secret and initial admin password | copy 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 -dThe 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 -dBuilding 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 --buildTroubleshooting
appnever 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 -dagain is safe.- Login succeeds but immediately returns to the login page —
BEDROCK_URLdoes 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 theHostheader, orBEDROCK_S3_URLis not the address the browser can reach. - Health check —
GET /loginreturns200from a healthy instance; the container's built-in health check uses the same request.