Spin supports different configuration providers for quiet a while right now. However, with the recent release of Spin 4.2.0 , we extended the list of supported configuration stores and added support for OpenBao .

OpenBao is an open source, community-driven secrets manager and fork of Vault managed by the Linux Foundation’s OpenSSF. (Source: openbao.org )

Your application keeps asking for a variable the same way it always did, but the value is fetched from OpenBao at runtime. In this article, I’ll run OpenBao in Docker, store a secret, and read it from a Rust Spin app.

Spin OSS and SpinKube only

The provider is configured through Spin’s runtime configuration. Runtime configuration is not supported on Akamai Functions, so this workflow applies to Spin OSS and SpinKube.

Why Env Vars And Runtime Config Fall Short

Spin reads application variables from the environment (SPIN_VARIABLE_*) or from --variable flags by default. Both end up in shell history, process listings, CI logs or deployment manifests. A static provider in the runtime config file does not help either: the secret is still sitting in a file next to the app.

With the OpenBao provider, the application only knows the name of a variable. The value lives in OpenBao and is looked up when the component asks for it.

Run OpenBao In Docker And Add Secrets

Start OpenBao in dev mode with a known root token. Dev mode keeps everything in memory, so it is perfect for a local setup and wrong for anything else:

BASH
# Run OpenBao in Docker
docker run --rm -d \
  --name openbao -p 8200:8200 \
  -e BAO_DEV_ROOT_TOKEN_ID=root \
  openbao/openbao server -dev -dev-listen-address=0.0.0.0:8200

The provider supports KV Secrets Engine version 2 only, at any mount point. Give the container a few seconds to finish its startup routine before you move on with enabling a dedicated spin mount and store a secret:

BASH
# Enable spin mount & store secret

# Alias the boilerplate
alias bao='docker exec -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=root openbao bao'

bao secrets enable -path=spin kv-v2
bao kv put spin/db_password value="s3cr3t-from-openbao"

Two details matter here. The secret is stored under the name of the Spin variable (db_password), and the value must be in a field called value. That is where the provider looks.

Scaffold The Spin App

BASH
# Create a new Spin app 
spin new -t http-rust -a openbao-demo
cd openbao-demo

Declare Secret Variables In spin.toml

Application variables are declared in the [variables] table. We’ll create a corresponding db_password variable and mark it as required and let Spin new it being a secret. With the variable in place, we can hand it to the component through [component.<name>.variables]:

TOML
# Update Application Manifest

# Add an application variable
[variables]
db_password = { required = true, secret = true }

# Grant the component read access to the variable
[component.openbao-demo.variables]
db_password = "{{ db_password }}"

There is no default and no value in the manifest. Anyone reading spin.toml learns that the app needs a db_password, nothing more. Declaring the variable with secret = true is optional: the OpenBao provider looks up every variable, secret or not. Because the variable is required, Spin refuses to start the app when no provider can supply it.

Configure The OpenBao Provider

Create rtc.toml next to spin.toml:

TOML
# Provide OpenBao runtime configuration
[[config_provider]]
type = "open_bao"
url = "http://127.0.0.1:8200"
token = "root"
mount = "spin"

url, token and mount are required. You can add an optional prefix, in which case Spin reads <prefix>/db_password below the mount instead. The token is a plain string in this file, so keep the file out of source control and out of the app’s artifacts.

The OpenBao and Vault providers share the same implementation, so type = "vault" works with the same keys. That means you can swap the backing service without touching the runtime configuration.

Providers are consulted in the order they are listed, and the environment variable provider always comes last. If OpenBao has no entry for a variable (HTTP 404), Spin continues with the next provider.

Read The Secret In A Rust HTTP Component

The component asks for the variable the way it would for any other:

RUST
use spin_sdk::http::{IntoResponse, Request, StatusCode};
use spin_sdk::{http_service, variables};

#[http_service]
async fn handle(_req: Request) -> impl IntoResponse {
    // try to load the variable using variables::get
    match variables::get("db_password").await {
        Ok(password) => {
            let p_len = password.len();
            (StatusCode::OK, format!("Secret has {} chars\n", p_len))
        },
        Err(e) => {
            println!("Could not read db_password: {e}");
            std::process::exit(1);
        }
    }
}

The handler never echoes the secret. It only reports its length, which is enough to prove the lookup works. If variables::get fails at runtime, there is no point in carrying on: log a clear message to stdout and terminate as early as possible.

Run And Verify

Pass the runtime config file to spin up:

BASH
# Build and Run the Spin App
spin build
spin up --runtime-config-file rtc.toml
BASH
# Send a GET request to the application
curl http://127.0.0.1:3000

The response should report a secret with 19 chars, the length of the value you stored.

When It Goes Wrong

  • Wrong path or variable name: OpenBao answers 404, Spin falls through to the environment provider, and the variable ends up undefined (a required variable then stops the app from starting). Check that the secret name matches the variable name and lives under the configured mount (and prefix).
  • Wrong or expired token: the lookup fails with an error instead of falling through. The provider does not retry or renew the token.
  • Wrong field name: the provider reads the value field. A secret stored under another field name cannot be resolved.
  • Unknown keys in the provider table: the runtime config is strict, so a misspelled key is rejected when Spin starts.

Recap

You’ve the freedom of choice for loading configuration data from either Vault , Azure Key Vault , or OpenBao .

The workflow remains the same. You declare the Spin variables, store their values in a KV v2 mount under the same name in a field called value. Point Spin at OpenBao with a [[config_provider]]. Spin won’t start the app if a required variable is missing, and if variables::get fails at runtime, you inspect stdout to figure out what went wrong while securing compute cycles.