openbao cheatsheet

Apr 29, 20265 min read
Apr 28, 2026 Cheatsheet/openbao 5 min read

Here's the corrected stack:

services:
  openbao:
    image: openbao/openbao:latest
    container_name: openbao
    restart: unless-stopped
    cap_add:
      - IPC_LOCK
    volumes:
      - /mnt/disk2/docker/bao/config:/openbao/config
      - /mnt/disk2/docker/bao/data:/openbao/data
    environment:
      - BAO_ADDR=http://0.0.0.0:8200
    command: server -config=/openbao/config/config.hcl
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.openbao.rule=Host(`bao.britbuzz.uk`)"
      - "traefik.http.routers.openbao.entrypoints=websecure"
      - "traefik.http.routers.openbao.tls.certresolver=myresolver"
      - "traefik.http.services.openbao.loadbalancer.server.port=8200"

networks:
  proxy:
    external: true

Your config.hcl at /mnt/disk2/docker/bao/config/config.hcl needs to exist before you deploy. Minimum working config:

storage "file" {
  path = "/openbao/data"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = true
}

ui = true

tls_disable = true is correct here — Traefik handles TLS externally, so the internal listener doesn't need it.


After deploying, since this is production mode (not dev), you'll need to initialise and unseal once:

# Init — run once, save the output somewhere safe
curl -s --request POST \
  --data '{"secret_shares":5,"secret_threshold":3}' \
  https://bao.britbuzz.uk/v1/sys/init

# Unseal — repeat 3 times with 3 different keys from the init output
curl -s --request POST \
  --data '{"key":"<unseal_key_N>"}' \
  https://bao.britbuzz.uk/v1/sys/unseal

The init response gives you 5 unseal keys and a root token — store these securely, they cannot be recovered if lost.


When fully unsealed the response will show "sealed": false. You can also check status anytime:

curl -s https://bao.britbuzz.uk/v1/sys/seal-status | jq .

5 — Enable the KV secrets engine

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"type":"kv","options":{"version":"2"}}' \
  https://bao.britbuzz.uk/v1/sys/mounts/secret

6 — Write and read your first secret

Write:

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"data":{"username":"admin","password":"supersecret"}}' \
  https://bao.britbuzz.uk/v1/secret/data/myapp/config

Read:

curl -s \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  https://bao.britbuzz.uk/v1/secret/data/myapp/config | jq .data.data

7 — Access the UI

Visit https://bao.britbuzz.uk/ui in your browser and log in with your root token. It's a full GUI for managing secrets, policies, and auth methods.


Important — after every container restart

OpenBao re-seals itself on restart. You'll need to re-run the 3 unseal curl commands (Step 4) each time. If you want to automate this, look into auto-unseal using a cloud KMS or a transit seal — but manual unseal is fine to start with.


That's a security feature — OpenBao (and Vault) prevent any auth method from issuing root-level tokens directly. You need to attach a policy instead.


Step 1 — Create an admin policy

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"policy":"path \"*\" { capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\", \"sudo\"] }"}' \
  https://bao.britbuzz.uk/v1/sys/policies/acl/admin

This creates a policy called admin that has full access to everything — effectively the same as root but allowed via userpass.


curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"type":"userpass"}' \
  https://bao.britbuzz.uk/v1/sys/auth/userpass

Step 2 — Create the user with the admin policy

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"password":"yourpassword","token_policies":"admin"}' \
  https://bao.britbuzz.uk/v1/auth/userpass/users/admin

Step 3 — Test the login

curl -s --request POST \
  --data '{"password":"yourpassword"}' \
  https://bao.britbuzz.uk/v1/auth/userpass/login/admin | jq .auth.client_token

You should get back a client token, and the UI login with username/password should now work without errors.


TOTP (Time-based One Time Password) in OpenBao works as a secrets engine that generates TOTP codes for you — it acts like an authenticator app (Google Authenticator, Authy etc). It's not used for logging into OpenBao itself, but rather for generating/validating TOTP codes for your other services.


Step 1 — Enable the TOTP secrets engine

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"type":"totp"}' \
  https://bao.britbuzz.uk/v1/sys/mounts/totp

Step 2 — Create a TOTP key for a service

This example creates a TOTP key for a service called gmail:

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{
    "generate": true,
    "issuer": "Gmail",
    "account_name": "[email protected]"
  }' \
  https://bao.britbuzz.uk/v1/totp/keys/gmail | jq .

The response will include a barcode (base64 PNG) and a url (otpauth:// string):

{
  "data": {
    "barcode": "iVBORw0KGgo...",
    "url": "otpauth://totp/Gmail:[email protected]?secret=XXXX&issuer=Gmail"
  }
}

Step 3 — Scan the QR code or import the URL

Option A — decode the barcode to a PNG and scan it:

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"generate":true,"issuer":"Gmail","account_name":"[email protected]"}' \
  https://bao.britbuzz.uk/v1/totp/keys/gmail | jq -r .data.barcode | base64 -d > totp-gmail.png

Then open totp-gmail.png and scan it with your authenticator app.

Option B — copy the url value and paste it directly into an authenticator app that accepts otpauth:// URLs.


Step 4 — Generate a TOTP code via curl

Once set up, you can ask OpenBao to generate the current 6-digit code:

curl -s \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  https://bao.britbuzz.uk/v1/totp/code/gmail | jq .data.code

Returns:

"123456"

Step 5 — Validate a TOTP code

You can also use OpenBao to validate a code (useful in scripts):

curl -s --request POST \
  --header "X-Vault-Token: $(jq -r '.root_token' init.json)" \
  --data '{"code":"123456"}' \
  https://bao.britbuzz.uk/v1/totp/code/gmail | jq .data.valid

Returns true or false.


Summary of how it fits together

What Who holds the secret
Normal authenticator app Your phone holds the TOTP seed
OpenBao TOTP engine OpenBao holds the TOTP seed, generates codes on demand

The advantage of storing TOTP seeds in OpenBao is that you have a centralised, backed-up, audited store of all your 2FA secrets — useful for shared team accounts or server-to-server authentication where a phone isn't practical.