Skip to content

OpenBao

Environment Variables

To use OpenBao, you need to set the following environment variables:

Key Value
SECRET_PROVIDER openbao
SECRET_PROVIDER_SITE_URL The URL of the OpenBao instance
SECRET_PROVIDER_ACCESS_TOKEN Access token for authenticating with the secret provider
SECRET_PROVIDER_ACCESS_TOKEN_FILE Path to a file containing the access token inside the container

Set SECRET_PROVIDER_ACCESS_TOKEN or SECRET_PROVIDER_ACCESS_TOKEN_FILE, not both.

For the environment variables that control automatic certificate rotation, see below.

Access token rotation

With SECRET_PROVIDER_ACCESS_TOKEN_FILE, Doco-CD reads the file again every time it fetches something from OpenBao. If the token in the file changes during runtime, the next request simply uses the new one and you don't have to restart anything. A token set with SECRET_PROVIDER_ACCESS_TOKEN is only read at startup. SOPS-encrypted token files work as well.

Doco-CD does not renew the token itself

Let something else handle that, for example OpenBao Agent, and have it write each new token to the file.

If the file is missing, empty or can't be decrypted, the request fails with an error. Doco-CD never falls back to the previous token. As soon as the token file is valid again, the next request works.

Mount the directory, not the file

Write the new token to a temporary file and move it over the old one, so Doco-CD never reads a half-written token. Then mount the directory that contains the token file. A single bind-mounted file keeps pointing at the old file after it is replaced on the host.

Deployment configuration

Add a mapping/reference between the environment variable you want to set in the docker compose project/stack and the reference to the key-value secret in OpenBao.

By default, the root namespace is used (root or /), but you can specify a different namespace by adding it as the first part of the reference.

  • A valid key-value secret reference should use the syntax:
    kv:<namespace(optional)>:<secretEngine>:<secretName>:<key>
    
  • A valid PKI certificate reference (read-only, fetches an already-issued certificate) should use the syntax:
    pki:<namespace(optional)>:<secretEngine>:<commonName>
    
  • A valid PKI role reference (issues a new certificate on every deployment/rotation, see Automatic Certificate Rotation) should use the syntax:
    pki-role:<namespace(optional)>:<secretEngine>:<role>:<commonName>
    

Examples of valid references:

  • kv:prod-secrets:db-prod:username → Fetches the username key from the db-prod key-value secret in the prod-secrets secret engine in the root namespace.
  • kv:root:prod-secrets:db-prod:username → Same as above, explicitly specifying the root namespace.
  • kv:my-namespace:secret:api-keys:stripe → Fetches the stripe key from the api-keys secret in the secret key-value secret engine in the my-namespace namespace.
  • pki:certs:myapp.example.com → Fetches the certificate for the common name myapp.example.com from the certs pki secret engine in the root namespace.
  • pki:my-namespace:certs:myapp.example.com → Fetches the certificate for the common name myapp.example.com from the certs pki secret engine in the my-namespace namespace.
  • pki-role:certs:myapp-role:myapp.example.com → Issues a new certificate for the common name myapp.example.com using the myapp-role PKI role in the certs secret engine in the root namespace.
  • pki-role:my-namespace:certs:myapp-role:myapp.example.com → Same as above, in the my-namespace namespace.

Example

For example in your .doco-cd.yml:

.doco-cd.yml
name: myapp
external_secrets:
  DB_USERNAME: kv:secret:db-prod:username
  DB_PASSWORD: kv:secret:db-prod:password
  CERT: pki:pki:myapp.example.com

To use the certificate in your compose file, you can pass the value to a compose config:

docker-compose.yml
configs:
  myapp-example-com.crt:
    #environment: CERT  # Either pass the variable via the environment like this (without a $ sign)
    content: $CERT  # Or use the content field to directly inject the variable value to the config content

services:
  app:
    image: myapp:latest
    environment:
      DB_USERNAME: $DB_USERNAME
      DB_PASSWORD: $DB_PASSWORD
    configs:
      - source: myapp-example-com.crt
        target: /etc/ssl/certs/example.crt

Full certificate chain

A pki: or pki-role: reference resolves to the leaf certificate only. Many TLS servers (nginx, HAProxy, Traefik, Postgres, …) instead expect a fullchain bundle: the leaf certificate followed by the intermediate and root CA certificates that signed it, so clients can build a complete trust path.

Every certificate reference therefore automatically exposes that bundle as an additional environment variable, named after the certificate's variable with a _FULL suffix — the same mechanism as the _KEY suffix used for private keys. An external secret named CERT resolves to:

  • CERT → the leaf certificate PEM
  • CERT_FULL → the leaf certificate followed by its issuing CA chain
  • CERT_KEY → the matching private key PEM (pki-role: references only)
.doco-cd.yml
name: myapp
external_secrets:
  CERT: pki-role:pki:myapp-role:myapp.example.com
docker-compose.yml
configs:
  myapp-fullchain.crt:
    environment: CERT_FULL
  myapp-cert.key:
    environment: CERT_KEY

services:
  app:
    image: myapp:latest
    configs:
      - source: myapp-fullchain.crt
        target: /etc/ssl/certs/myapp-fullchain.crt
      - source: myapp-cert.key
        target: /etc/ssl/private/myapp.key

The _FULL value is a single PEM bundle, with the leaf certificate always first:

-----BEGIN CERTIFICATE-----   <- leaf certificate (the one issued for your common name)
-----BEGIN CERTIFICATE-----   <- intermediate CA (if any)
-----BEGIN CERTIFICATE-----   <- root CA

How the chain is determined depends on the reference type:

  • pki-role: references use the ca_chain field of the issue response, falling back to issuing_ca when the mount does not return a chain.
  • pki: references use the chain of the issuer that actually signed the certificate, so certificates from a non-default issuer on a multi-issuer mount get the correct chain. The issuers are listed via <secretEngine>/issuers and read via <secretEngine>/issuer/<id>/json, both unauthenticated endpoints, so no additional policy is required. When several issuers share the signing key (e.g. a reissued CA), the currently valid one is preferred. The mount's default chain (<secretEngine>/cert/ca_chain) is only used when all issuers could be read and none of them signed the certificate. If some issuers could not be read and none of the readable ones matches, resolving the reference fails instead of returning a possibly wrong chain.

When no chain is available at all, _FULL holds the leaf certificate on its own.

Note

Use CERT where a leaf-only certificate is expected and CERT_FULL where a bundle is; both are always set, so you can switch without touching the reference. Automatic certificate rotation covers all three entries: expiry and serial tracking always read the leaf from CERT, and services consuming CERT_FULL are recreated on rotation just like those consuming CERT or CERT_KEY.

Warning

Because the _FULL and _KEY entries are generated, an external secret of your own may not use those names. Mapping both CERT and CERT_FULL in external_secrets is rejected with a conflict error.

Automatic Certificate Rotation

Certificates issued via a pki-role: reference (see above) are eligible for automatic rotation: doco-cd runs a background watcher that tracks the expiry of every deployment whose certificate(s) were all issued through pki-role: refs, and automatically reissues the certificate(s) and redeploys the project before they expire.

Note

Deployments that use the read-only pki: reference are not eligible for automatic rotation, since that reference only reads an already-issued certificate and cannot reissue a new one. Use pki-role: if you want doco-cd to keep your certificates renewed automatically.

Enabling automatic rotation

Automatic certificate rotation is currently only supported for OpenBao PKI roles:

Key Type Description Default
CERT_ROTATION_ENABLED boolean Enables the built-in automatic certificate rotation watcher. false
CERT_ROTATION_THRESHOLD duration How far ahead of a certificate's expiry doco-cd triggers automatic rotation. Accepts a Go duration (e.g. 72h). 72h
CERT_ROTATION_CHECK_INTERVAL duration How often the certificate rotation watcher checks deployed certificates for upcoming expiry. Accepts a Go duration. 1h

How it works

  1. When a deployment resolves a pki-role external secret, doco-cd stamps three labels on the deployed container(s)/service(s):
    • cd.doco.deployment.cert.expiry: the earliest expiry (RFC3339) among all certificates in the deployment.
    • cd.doco.deployment.cert.rotatable: true only if every certificate-bearing external secret in the deployment used a pki-role reference.
    • cd.doco.deployment.cert.state: the reference and serial number of every deployed pki-role certificate, used to detect revocation.
  2. The watcher periodically (every CERT_ROTATION_CHECK_INTERVAL) lists all deployments labeled cert.rotatable=true and rotates a deployment when either is true:
    • its recorded expiry is within CERT_ROTATION_THRESHOLD (or already past),
    • or any certificate in its cert.state label has since been revoked in OpenBao.
  3. When a deployment is due, doco-cd reloads its deploy config and re-resolves all external secrets. It issues brand-new certificates and keys for every pki-role reference, then redeploys to pick up the fresh values:

    Only services that actually consume a rotated certificate or private key (via direct environment variables or configs/secrets entries) get recreated; unrelated services in the same project are left untouched.

Private key access

A pki-role: reference automatically exposes the certificate's matching private key as a second environment variable, named after the certificate's variable with a _KEY suffix. For example, an external secret named CERT referencing pki-role:... resolves to both:

  • CERT → the leaf certificate PEM
  • CERT_KEY → the matching private key PEM

The full certificate chain is exposed alongside them as CERT_FULL.

.doco-cd.yml
name: myapp
external_secrets:
  CERT: pki-role:pki:myapp-role:myapp.example.com
docker-compose.yml
configs:
  myapp-cert.crt:
    environment: CERT
  myapp-cert.key:
    environment: CERT_KEY

services:
  app:
    image: myapp:latest
    configs:
      - source: myapp-cert.crt
        target: /etc/ssl/certs/myapp.crt
      - source: myapp-cert.key
        target: /etc/ssl/private/myapp.key

Examples

mTLS

To secure service-to-service communication with mutual TLS (mTLS), issue a client certificate for your application through a PKI role dedicated to mTLS clients, and let doco-cd keep it renewed automatically:

.doco-cd.yml
name: myapp
external_secrets:
  MTLS_CERT: pki-role:pki:mtls-client-role:myapp.internal
docker-compose.yml
configs:
  myapp-mtls.crt:
    content: $MTLS_CERT
  myapp-mtls.key:
    content: $MTLS_CERT_KEY

services:
  app:
    image: myapp:latest
    configs:
      - source: myapp-mtls.crt
        target: /etc/ssl/certs/mtls-client.crt
      - source: myapp-mtls.key
        target: /etc/ssl/private/mtls-client.key

Enable CERT_ROTATION_ENABLED=true on the doco-cd instance, and the client certificate will be reissued and redeployed automatically before it expires, so mTLS handshakes never fail due to an expired client certificate.

Private PKI

For internal services that need a server certificate signed by your own private Certificate Authority, define a PKI role that constrains what common names/domains it may issue for (e.g. allowed_domains: internal.example.com), then reference it the same way:

.doco-cd.yml
name: internal-service
external_secrets:
  SERVER_CERT: pki-role:pki:internal-services-role:api.internal.example.com
docker-compose.yml
configs:
  server.crt:
    content: $SERVER_CERT
  server.key:
    content: $SERVER_CERT_KEY

services:
  app:
    image: internal-service:latest
    configs:
      - source: server.crt
        target: /etc/ssl/certs/server.crt
      - source: server.key
        target: /etc/ssl/private/server.key

With CERT_ROTATION_ENABLED=true, doco-cd continuously monitors the certificate's expiry and automatically reissues + redeploys the service well ahead of expiry, so your internal PKI-secured services stay trusted without any manual certificate management.