> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenBao

> Deploy OpenBao on Control Plane using the Template Catalog. The Linux Foundation's open-source fork of HashiCorp Vault — an identity-based secrets engine with a Vault-compatible API and CLI, integrated raft storage, and hands-free auto-unseal from a platform secret, AWS KMS, or GCP Cloud KMS.

## Overview

OpenBao is the Linux Foundation's open-source (MPL-2.0) fork of HashiCorp Vault: an identity-based secrets engine with a Vault-compatible API and CLI, covering KV v2 storage, dynamic secrets, PKI, and transit encryption. This template deploys a single-node OpenBao server with integrated raft storage on a persistent volume set, and auto-unseal so the server comes back on its own after every restart.

If you are choosing between catalog templates: OpenBao is the Vault-compatible engine for workloads that need dynamic secrets, PKI, transit, or existing Vault tooling, while [Infisical](/template-catalog/templates/infisical) is an application-secrets workflow platform with a team-oriented web UI. The two solve different problems and can be run side by side.

### Architecture

* **OpenBao server** — A single `stateful` workload serving the HTTP API and web UI on port `8200`, configured from a rendered HCL config file. Storage is OpenBao's integrated raft backend, written to a persistent volume set at `/openbao/data`.
* **Auto-unseal** — The encryption barrier is unwrapped automatically on every boot in one of three seal modes: `static` (a 32-byte key held in a Control Plane secret you create), `awskms`, or `gcpckms`. Both KMS modes are keyless — the workload identity reaches the KMS key through a Control Plane [cloud account](https://docs.controlplane.com/guides/create-cloud-account), with no static cloud credentials in the container.
* **Private by default** — External access is off (`publicAccess.enabled: false`); the server is reachable only from inside the GVC until you turn it on.

### What Gets Created

* **Stateful OpenBao Workload** — One replica serving the API and UI on port `8200`.
* **Volume Set** — Persistent raft storage for all secrets, auth configuration, and cluster state. A final snapshot is retained for 7 days when the volume set is deleted.
* **Config Secret** — An opaque secret holding the rendered HCL server config, file-mounted into the container.
* **Identity & Policy** — An identity bound to the workload with `reveal` on exactly the secrets it mounts. In the KMS seal modes the identity also carries the keyless cloud access used to reach the KMS key.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

Prerequisites depend on the seal mode you choose.

**Static seal (the default): create the unseal-key secret before installing.** The workload references it by name (`seal.static.secretName`, default `my-openbao-unseal-key`) and the deployment wedges until the secret exists. Create it as an **opaque** secret holding a 32-byte key:

```bash theme={null}
printf '%s' "$(openssl rand -hex 32)" | cpln secret create-opaque --name my-openbao-unseal-key --encoding plain -f -
```

<Warning>
  The unseal key is **write-once — never change or delete this secret.** It wraps OpenBao's encryption barrier; losing or rotating the key makes every stored secret permanently unrecoverable. Back the key up somewhere safe outside this instance.
</Warning>

**KMS seal modes (`awskms` / `gcpckms`)** need a KMS key, a Control Plane cloud account, and a key-scoped grant instead — see [Auto-unseal setup](#auto-unseal-setup-kms-modes). No unseal-key secret is required in these modes.

Install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
openbao:
  # Official image; 2.6.x pinned deliberately (2.7 moves KMS seals to external plugins)
  image: openbao/openbao:2.6.1
  resources:
    minCpu: 250m
    maxCpu: 1000m
    minMemory: 256Mi
    maxMemory: 1Gi

# How the encryption barrier is unwrapped on boot. One mode only.
seal:
  type: static              # static | awskms | gcpckms

  # static: auto-unseal from a key in a platform secret. PREREQUISITE — create the
  # opaque secret BEFORE install (a missing secret wedges the deployment):
  #   printf '%s' "$(openssl rand -hex 32)" | cpln secret create-opaque --name my-openbao-unseal-key --encoding plain -f -
  # WRITE-ONCE: losing or changing this key makes all stored data unrecoverable.
  static:
    secretName: my-openbao-unseal-key

  # awskms: keyless auto-unseal via AWS KMS through a Control Plane cloud account.
  # Create the KMS key + a scoped IAM policy before install.
  aws:
    kmsKeyId: arn:aws:kms:us-east-1:111111111111:key/my-openbao-kms-key # key ARN (or ID/alias)
    region: us-east-1
    cloudAccountName: my-aws-cloud-account
    policyName: my-openbao-kms-policy # customer-managed IAM policy granting Encrypt/Decrypt/DescribeKey on the key

  # gcpckms: keyless auto-unseal via GCP Cloud KMS through a Control Plane cloud account.
  gcp:
    project: my-gcp-project
    region: global # key ring location
    keyRing: my-openbao-keyring
    cryptoKey: my-openbao-key
    cloudAccountName: my-gcs-cloud-account

volumeset:
  capacity: 10 # GiB (minimum 10) — raft data: all secrets, auth config, cluster state

publicAccess:
  enabled: false # true = HTTPS API + web UI on the auto *.cpln.app endpoint
internalAccess:
  type: same-gvc # none | same-gvc | same-org | workload-list
  workloads: [] # used only with workload-list
```

### OpenBao Server

* `openbao.image` — The official OpenBao image. The template pins `2.6.x` deliberately: OpenBao 2.7 moves the KMS auto-unseal mechanisms into external plugins, so a blind bump would break the `awskms` and `gcpckms` seal modes.
* `openbao.resources` — CPU and memory for the server container. The defaults run a single node comfortably; raise `maxMemory` for large secret sets or heavy PKI use.

### Seal and Auto-unseal

`seal.type` selects exactly one mode, and only that mode's sub-block is used:

| Mode               | How the barrier is unwrapped                                                | What you must provide                                                                                                       |
| ------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `static` (default) | From a 32-byte key held in a Control Plane opaque secret                    | The prerequisite secret, named in `seal.static.secretName`                                                                  |
| `awskms`           | Keyless AWS KMS encrypt/decrypt through a Control Plane cloud account       | KMS key, cloud account, and a scoped IAM policy — see [AWS KMS setup](#auto-unseal-setup-kms-modes)                         |
| `gcpckms`          | Keyless GCP Cloud KMS encrypt/decrypt through a Control Plane cloud account | Key ring, crypto key, cloud account, and a post-install key grant — see [GCP Cloud KMS setup](#auto-unseal-setup-kms-modes) |

In every mode the server unseals itself on boot with no operator action. In the KMS modes there is no static credential anywhere: the container is issued no cloud access keys, and no unseal key is mounted.

<Warning>
  **Do not change `seal.type` after the server has been initialized.** The seal wraps the existing data, so switching modes requires an OpenBao seal migration — it is not a values change.
</Warning>

### Storage

* `volumeset.capacity` — Initial volume size in GiB (minimum 10). This volume holds the raft store: every secret, auth mount, policy, and the cluster state.

Data survives restarts, redeployments, and `helm upgrade`. Uninstalling deletes the volume set (a final snapshot is kept for 7 days), so a reinstall comes up uninitialized.

### Access

* `publicAccess.enabled` — `false` by default. When set to `true`, the API and web UI are served over HTTPS on the automatically assigned `*.cpln.app` canonical endpoint. While it is `false`, external requests are rejected at the edge.
* `internalAccess.type` — Internal firewall scope of the server workload:

| Type            | Description                                                                                                          |
| --------------- | -------------------------------------------------------------------------------------------------------------------- |
| `none`          | No internal access.                                                                                                  |
| `same-gvc`      | Allow access from all workloads in the same GVC (default).                                                           |
| `same-org`      | Allow access from all workloads in the same organization.                                                            |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`, e.g. `//gvc/GVC_NAME/workload/WORKLOAD_NAME`. |

## Auto-unseal Setup (KMS Modes)

Skip this section if you are using the default `static` seal.

<Tabs>
  <Tab title="AWS KMS">
    Access is keyless: the workload identity federates into your AWS account through a Control Plane cloud account, so no credentials are stored in the container or in your values.

    <Steps>
      <Step title="Create or pick a symmetric KMS key">
        Set `seal.aws.kmsKeyId` to the key ARN (an ID or alias also works) and `seal.aws.region` to the key's region.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create an AWS Cloud Account](https://docs.controlplane.com/guides/create-cloud-account). Set `seal.aws.cloudAccountName` to its name.
      </Step>

      <Step title="Create a scoped IAM policy">
        Create an IAM policy with the JSON below, replacing the resource with your key ARN, and set `seal.aws.policyName` to the policy name.

        ```json theme={null}
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "kms:Encrypt",
                        "kms:Decrypt",
                        "kms:DescribeKey"
                    ],
                    "Resource": "arn:aws:kms:us-east-1:111111111111:key/YOUR_KEY_ID"
                }
            ]
        }
        ```
      </Step>
    </Steps>

    No post-install step is needed — the identity carries the policy from the moment it is created, and the server boots ready (uninitialized) as usual.
  </Tab>

  <Tab title="GCP Cloud KMS">
    GCP access is keyless as well, but the key grant is applied **after** install, because the service account name only exists once the identity has been created.

    <Steps>
      <Step title="Create a key ring and crypto key">
        Create a symmetric crypto key in Cloud KMS and fill in `seal.gcp.project`, `seal.gcp.region` (the key ring location, e.g. `us-east1`), `seal.gcp.keyRing`, and `seal.gcp.cryptoKey`.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a GCP Cloud Account](https://docs.controlplane.com/guides/create-cloud-account). Set `seal.gcp.cloudAccountName` to its name.
      </Step>

      <Step title="Install, then grant access on that one key">
        Read the generated service account name from the identity, then grant both required roles on the crypto key:

        ```bash theme={null}
        cpln identity get {release}-openbao-identity --gvc {gvc} -o json | jq -r '.status.objectName'
        # the service account is {objectName}@{project}.iam.gserviceaccount.com

        SA={objectName}@{project}.iam.gserviceaccount.com
        for ROLE in roles/cloudkms.cryptoKeyEncrypterDecrypter roles/cloudkms.viewer; do
          gcloud kms keys add-iam-policy-binding {cryptoKey} \
            --keyring {keyRing} --location {region} --project {project} \
            --member "serviceAccount:$SA" --role "$ROLE"
        done
        ```
      </Step>
    </Steps>

    Both roles are required: `cryptoKeyEncrypterDecrypter` performs the seal and unseal, and `cloudkms.viewer` supplies the `cloudkms.cryptoKeys.get` call OpenBao makes at startup. Grant them on the key itself, never project-wide.

    <Warning>
      **Until the grant exists, the workload crash-loops** with `PermissionDenied … cloudkms.cryptoKeys.get`. This is expected before the grant, and it heals itself roughly 30 seconds afterwards — no redeploy or reinstall needed. To confirm the identity reached GCP, check `status.gcp.usable` in `cpln identity get {release}-openbao-identity --gvc {gvc} -o yaml`.
    </Warning>
  </Tab>
</Tabs>

## Initializing OpenBao

The server boots reachable but **uninitialized** — that is the expected state right after install. Initialize it exactly once:

```bash theme={null}
cpln workload exec {release}-openbao --gvc {gvc} --container openbao -- bao operator init
```

<Warning>
  **Save the printed recovery keys and initial root token immediately.** They are shown once, only in your terminal, and are stored nowhere — the template never sees them and cannot recover them.
</Warning>

From then on every restart auto-unseals with zero manual steps; a replaced replica is back unsealed and serving in roughly two minutes. Log in with `bao login <root-token>` or through the web UI, create scoped auth (for example userpass or OIDC), then revoke the root token per standard practice.

The `bao` CLI is present in the container with `BAO_ADDR` preset, so any `cpln workload exec … -- bao …` command works without extra flags — for example `bao status` to check the seal state.

## Connecting

| Target                       | Address                                          | Credentials                 |
| ---------------------------- | ------------------------------------------------ | --------------------------- |
| Internal (same GVC)          | `http://{release}-openbao.{gvc}.cpln.local:8200` | OpenBao token / auth method |
| Public API + UI (if enabled) | `https://<canonical>.cpln.app` (UI at `/ui/`)    | OpenBao token / auth method |
| Health                       | `GET /v1/sys/health`                             | none                        |

External clients use `https://` — the platform edge terminates TLS. Same-GVC clients use plain `http://` over the mesh's mTLS. The canonical hostname appears under `status.canonicalEndpoint` in `cpln workload get {release}-openbao --gvc {gvc} -o yaml`.

Because the API is Vault-compatible, existing Vault clients, SDKs, and the `vault` CLI can talk to this endpoint by pointing their address at it.

## Important Notes

* **Static mode: the unseal-key secret must exist before install, and it is write-once.** A missing secret wedges the deployment; a lost or changed key makes all stored data unrecoverable.
* **Run `bao operator init` once after install and save the output** — the recovery keys and root token are printed a single time, to your terminal only.
* **Do not switch `seal.type` after initialization** — changing modes requires an OpenBao seal migration, not a values change.
* **`gcpckms` needs its key grant applied after install** — the workload crash-loops on `cloudkms.cryptoKeys.get` until both roles are granted on the key, then recovers on its own.
* **Private by default** — set `publicAccess.enabled: true` to expose the API and web UI on the canonical endpoint.
* **Data survives restarts and upgrades; uninstall deletes the volume set** (a final snapshot is kept for 7 days), and a reinstall starts uninitialized.
* **Uninstall does not delete your unseal-key secret** — it is your resource, created outside the release, and it stays until you remove it.
* **The template deploys a single node** — raft runs with one member, so plan maintenance windows around the short restart gap rather than expecting failover.

## External References

<CardGroup cols={2}>
  <Card title="OpenBao Documentation" icon="book" href="https://openbao.org/docs/">
    Official OpenBao documentation
  </Card>

  <Card title="Seal / Auto-unseal Configuration" icon="lock" href="https://openbao.org/docs/configuration/seal/">
    Reference for the static, awskms, and gcpckms seal stanzas
  </Card>

  <Card title="Raft Storage" icon="database" href="https://openbao.org/docs/configuration/storage/raft/">
    The integrated storage backend this template uses
  </Card>

  <Card title="operator init" icon="terminal" href="https://openbao.org/docs/commands/operator/init/">
    The one-time initialization command and its options
  </Card>

  <Card title="OpenBao on GitHub" icon="github" href="https://github.com/openbao/openbao">
    Source, releases, and issue tracker
  </Card>

  <Card title="OpenBao Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/openbao">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
