Skip to content

Installation and initialization

This phase covers the core technical execution of the on-premise deployment. Before starting, ensure you have completed all prerequisite planning and provisioning:

  • You have provisioned, networked, and made accessible all infrastructure (database, message queue, HSM). (See Installation environment setup.)
  • You have access to Ripple Custody (Vault) container images (either directly from Ripple's registry or mirrored to your private registry).
  • You have verified all container image signatures using cosign.
  • You have prepared your deployment workstation with Helm, kubectl, and cosign.

Overview

You will not deploy the entire platform at once. The installation is a multi-stage process. First, deploy the core API. Then use that API to initialize the security components. Finally, use the output of that initialization to deploy the rest of the platform.

The installation will follow these 8 steps:

  1. Verify prerequisites: Confirm you have completed Installation environment setup and have the Helm charts ready.
  2. Initial configuration: Configure your own production.yaml with all known prerequisites (DB, MQ, registry, etc.) but leave the secure components disabled.
  3. Deploy core services: Run helm install to deploy the user-facing services (API, Web UI, OIDC/Auth).
  4. Deploy and initialize the notary: Run helm upgrade to deploy the notary. Then, make a Genesis API call to force the notary to generate its master key. Save the public key from this call.
  5. Check progress: After step 4, you should be able to login to Web UI.
  6. Deploy vault and ledgers: Run helm upgrade to deploy the vault (configured with the notary's public key) and all your blockchain indexers.
  7. Register your vault: Manually register the deployed vault instance via the Web UI or API.
  8. Verify your installation: Create a blockchain account to confirm all components are operational.

Step 1: Verify prerequisites

Before you begin: This step assumes you have completed Installation environment setup, including:

  • Setting up your deployment workstation with Helm, kubectl, and cosign
  • Obtaining and verifying all container images
  • Obtaining the Helm chart package from Ripple
  1. Unpack the Helm chart package on your deployment workstation:

    tar -xvf ripple-custody-helm-charts-vX.Y.Z.tgz
    cd ripple-custody
  2. Create a custom YAML configuration file (e.g., production.yaml) using one of the provided example files as a starting point. This file becomes your single source of truth for deployment settings.

    Do not set any sensitive data in this file; use a dedicated Secrets Manager or Kubernetes Secrets instead.

Step 2: Initial production.yaml configuration

Before your first deployment, edit your configuration file (e.g. production.yaml) to connect to the infrastructure prerequisites you provisioned earlier.

At this stage, you only configure the known items. You enable the notary, vault, and indexers sections in later steps.

Key configuration sections:

# production.yaml

# 1. Container Registry Configuration
# Option A: Direct connection to Ripple's registry (standard)
harmonize:
  repository:
    base: "metaco.azurecr.io/custody"
    pullSecrets:
      - "metaco-creds"  # Kubernetes secret with Ripple registry credentials

# Option B: Private registry (if you mirrored images)
# harmonize:
#   repository:
#     base: "my-artifactory.my-bank.com/harmonize"
#     pullSecrets:
#       - "my-registry-pull-secret"

# 2. Network & TLS Configuration
# Define the public-facing hostnames for your services
  urls:
    base: "custody.my-bank.com"  # Base URL for all subdomains
    tls: true  # Ensure TLS is enabled for production

    # These URLs will automatically use the base URL and defined subdomains:
    # frontend: custody.my-bank.com
    # api: api.custody.my-bank.com
    # auth: auth.custody.my-bank.com

# 3. Database (PostgreSQL) Connection
  postgresql:
    host: "db-custody.my-bank.local"
    port: "5432"  # Must be a string
    database: "custody_db"
    username: "custody_user"
    sslmode: "verify-full"  # Recommended for production
    # NOTE: Password should be injected via Kubernetes secret or external secrets manager

# 4. Message Queue (RabbitMQ) Connection
  externalAmqp:
    enabledForCoreComponents: true
    host: "mq-custody.my-bank.local"
    port: 5671  # TLS port
    username: "custody_mq_user"
    vhost: "harmonize"
    sslEnabled: true
    existingSecret: "mq-password-secret"  # Reference to Kubernetes secret

# 5. Disable Secure Components (you enable these in later steps)
  notary:
    protocol: grpc  # Set protocol but keep disabled

components:
  notary:
    enabled: false  # Will enable in Step 4

harmonize:
  vaults:
    "00000000-0000-0000-0000-000000000000":
      enabled: false  # Will enable in Step 6

  # Indexers - all disabled initially
  ledgers:
    bitcoin-mainnet:
      enabled: false
    ethereum-mainnet:
        enabled: false
    # ... ensure all other ledgers are set to enabled: false

Note on production.yaml file size: In production deployments, the production.yaml file can exceed 3000 lines. It contains detailed configuration for all components, ledgers, and environments. This guide shows simplified examples for clarity. For complete configuration examples, contact your Ripple liaison.

Step 3: Deploy the core services

Your first deployment will only bring up the "Core" services (API, Web UI, OIDC/Auth, and related backends).

  1. From your Helm chart directory, run the install command using your configuration file:

    helm install harmonize . \
      -f production.yaml \
      --namespace custody-core \
      --create-namespace
  2. Verify DNS and TLS: The output shows a success message. Verify that your DNS names and TLS certificates work by accessing the Web UI URL. The page should load, confirming your Ingress, DNS, and TLS configuration.

    Test access:

    # Test DNS resolution
    nslookup custody.my-bank.com
    
    # Test TLS and web UI
    curl -v https://custody.my-bank.com

Step 4: Deploy and initialize the notary (Genesis call)

Now that the API is running, you will deploy the notary, connect it to your HSM, and make the "Genesis Call" to initialize its master key.

  1. Configure notary and HSM in production.yaml:

    • Set harmonize.notary.protocol: grpc (recommended for production).
    • Configure the notary's KMS platform by setting components.notary.platform to one of the supported production values:
      • kms_luna - Thales Luna HSM (hardware HSM)
      • kms_blocksafe - BlockSafe HSM (hardware HSM)
      • kms_ibm - IBM HSM for deployments where the notary and/or vault are deployed on s390x architecture
      • kms_aws - AWS CloudHSM (cloud HSM)
    • Fill in the required platform-specific section (e.g., components.notary.kms_luna) with your HSM's connection details. This enables and configures the "KMS Connect" sidecar container that manages the HSM connection.
    s390x support

    Starting in version 1.34, Ripple Custody (Vault) supports s390x (IBM) architecture for secure components only (the notary and vault). Non-secure components are not supported on s390x architecture.

    Example configuration for Luna HSM:

    # production.yaml
    harmonize:
      notary:
        protocol: grpc  # Use grpc for production (better performance)
    
    components:
      notary:
        platform: kms_luna
        kms_luna:
          host: "hsm-notary.my-bank.local"
          port: "1792"
          slot: "1"
          pin: "notary-hsm-pin-secret"  # Use Kubernetes secrets for production
          client:
            certificate: |
              -----BEGIN CERTIFICATE-----
              ...
              -----END CERTIFICATE-----
            key: |
              -----BEGIN PRIVATE KEY-----
              ...
              -----END PRIVATE KEY-----
          server:
            certificate: |
              -----BEGIN CERTIFICATE-----
              ...
              -----END CERTIFICATE-----

    For complete configuration examples for all KMS platforms, see the platform-specific guides in the KMS Integration section.

  2. Upgrade the Deployment to deploy the notary bridge and notary pods:

    helm upgrade harmonize . \
      -f production.yaml \
      --namespace custody-core
  3. Monitor the notary pod. It will connect to the HSM, generate its key, and then wait for the Genesis Call.

  4. Perform the Genesis API Request:

    This is a one-time, unauthenticated API operation. It initializes the entire system state (Root Domain, initial Users, and Policy setup). The notary generates and registers its master public key during this step.

    Genesis API call:

    curl -X POST https://custody.my-bank.com/v1/genesis \
      -H "Content-Type: application/json" \
      -d @genesis-payload.json

    Simplified Genesis payload example:

    {
      "rootDomainSetup": {
        "id": "e7eb7b63-d63b-4c30-9cca-d3b1ea7e46ad",
        "alias": "Root Domain",
        "lock": "Unlocked",
        "governingStrategy": "CoerceDescendants",
        "permissions": {
          "readAccess": {
            "domains": ["operator"],
            "users": ["operator"],
            "accounts": ["operator"],
            "transactions": ["operator"]
          }
        },
        "users": [
          {
            "id": "e1fe3431-f899-427b-9e67-a75fb9fe5e57",
            "alias": "admin@my-bank.com",
            "publicKey": "MFkw...NfPfw/q9YW74Q==",
            "roles": ["manager"],
            "loginIds": [
              {
                "id": "admin@my-bank.com",
                "providerId": "harmonize"
              }
            ],
            "lock": "Unlocked"
          }
        ],
        "policies": [
          {
            "id": "00000000-0000-0000-0000-000000000000",
            "alias": "Catch-All Policy",
            "rank": 20,
            "workflow": [
              {
                "role": "manager",
                "quorum": 1,
                "type": "RoleQuorum"
              }
            ]
          }
        ]
      },
      "cryptoSetup": {
        "apiSigning": "Secp256r1",
        "collectionSigning": "Secp256r1",
        "messageSigning": "Ed25519"
      }
    }

    You can only run the Genesis operation once. The system has an anti-rewind mechanism that blocks any attempt to rerun it. Plan your initial domain structure, users, and policies carefully.

    For detailed explanations of each Genesis payload section, see Genesis payload reference below.

    Upon successful execution (HTTP 200), the system initializes. The response payload contains:

    • The notary public key (notaryPublicKey) - Save this value, you will need it in Step 6
    • The vault-trusted public key for messaging (VAULT_TRUSTED_SIG) - Save this value, you will need it in Step 6

Step 5: Check you can login

As an intermediary step, after initializing your instance via the Genesis, confirm that you can log in using the login details of one of the user profiles you configured in the Genesis.

Step 6: Deploy vault and ledgers

In this final step, you will deploy the vault and the blockchain indexers.

  1. Configure vault in production.yaml: The vault configuration is similar to that for the notary — you select a KMS platform and provide the connection details.

    Key configuration sections:

    • Set the vault KMS platform under harmonize.vaults."00000000-0000-0000-0000-000000000000".platform
    • Configure the platform-specific settings (e.g., kms_luna, kms_ibm, kms_aws, etc.)
    • Critical: Set harmonize.vaults."00000000-0000-0000-0000-000000000000".notary_public_key to the notary's public key you saved from the Genesis response

    Example Vault configuration for Luna HSM:

    # production.yaml
    harmonize:
      vaults:
        "00000000-0000-0000-0000-000000000000":  # Default vault UUID
          enabled: true
          platform: kms_luna
          notary_public_key: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149"  # From Genesis response
          kms_luna:
            host: "hsm-vault.my-bank.local"
            port: "1792"
            slot: "2"
            pin: "vault-hsm-pin-secret"  # Use Kubernetes secrets
            client:
              certificate: |
                -----BEGIN CERTIFICATE-----
                ...
                -----END CERTIFICATE-----
              key: |
                -----BEGIN PRIVATE KEY-----
                ...
                -----END PRIVATE KEY-----
            server:
              certificate: |
                -----BEGIN CERTIFICATE-----
                ...
                -----END CERTIFICATE-----

    For complete vault KMS configuration examples, see KMS integration.

  2. Configure Ledgers (Blockchain Indexers) in production.yaml: Enable the blockchain networks you want to support. Each ledger requires connection to blockchain nodes.

    Blockchain Node Authentication: Depending on whether you're self-hosting nodes or using third-party providers, you may need to configure authentication:

    • Self-hosted nodes: May require basic authentication (username/password) or no authentication if on private network
    • Third-party providers (Infura, Alchemy, QuickNode, etc.): Require API keys in the node URL
    • Enterprise nodes: May require mTLS certificates or API tokens

    Example Ledger configuration:

    # production.yaml
    harmonize:
      ledgers:
        bitcoin-mainnet:
          enabled: true
          node:
            uri: "https://bitcoin-node.my-bank.local:8332"
            # For basic auth: "https://user:password@bitcoin-node.my-bank.local:8332"
        ethereum-mainnet:
          enabled: true
          node:
            uri: "https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY"
            # Or self-hosted: "https://ethereum-node.my-bank.local:8545"

    For complete ledger configuration including all supported networks and authentication methods, see Ledgers Configuration Guide.

  3. Deploy vault and Indexers:

    helm upgrade harmonize . \
      -f production.yaml \
      --namespace custody-core
    
    # Deploy vault in separate namespace
    helm upgrade harmonize-vault . \
      -f production.yaml \
      --namespace custody-vault \
      --create-namespace
  4. Verify vault and Indexer pods are running:

    # Check the core services and indexers
    kubectl get pods -n custody-core
    
    # Check the vault
    kubectl get pods -n custody-vault

    All pods should be in Running state.

Step 7: Register the vault

Critical: Before you can use the vault to create blockchain accounts, you must register it in Ripple Custody (Vault). This creates the vault entity in the governance system.

Note

When you open a new vault, set the key management system (KMS). Choose either HSM (hardware-security module) or MPC (multi-party computation). This setting enables correct behavior and does not affect existing vaults. For more information about MPC key management, see Multi-party computation (MPC) overview.

Obtain the vault public key

To register the vault, you first need to obtain the public key.

  1. Access the vault logs according to the method supported for your architecture:

    kubectl logs -n custody-vault <vault-pod-name> -c vault-core | grep "pubKeySig"
  2. Look for the generated public key:

    [INFO] [] [InitSvc]: Vault Core pubKeySig (der base64): MCowBQYDK2VwAyEA0rG1QbK6fGtkgsf7XXdbTFJVojK30tNEKrcjicKeVbI=

    Copy the base64-encoded public key (e.g., MCowBQYDK2VwAyEA0rG1QbK6fGtkgsf7XXdbTFJVojK30tNEKrcjicKeVbI=).

Register the vault

Register the vault using either the UI or API:

Option A: Register via UI

  • Navigate to Administration > Vaults
  • Select Create a vault
  • Enter the vault details:
    • Vault ID: 00000000-0000-0000-0000-000000000000 (the default vault UUID from your configuration)
    • Alias: A friendly name (e.g., "Production vault")
    • KMS Type: Select HSM (or MPC if using MPC)
    • Public Key: Paste the public key from step 1
  • Select Submit for Approval and sign the operation

Option B: Register via API

Submit an intent to create the vault entity:

curl -X POST https://custody.my-bank.com/v1/intents \
  -H "Content-Type: application/json" \
  -d '{
    "request": {
      "author": {
        "id": "<YOUR_USER_ID>",
        "domainId": "<ROOT_DOMAIN_ID>"
      },
      "expiryAt": "2025-12-31T23:59:59Z",
      "targetDomainId": "<ROOT_DOMAIN_ID>",
      "id": "<NEW_INTENT_UUID>",
      "payload": {
        "id": "00000000-0000-0000-0000-000000000000",
        "alias": "Production vault",
        "parameters": {
          "type": "HSM"
        },
        "publicKey": "MCowBQYDK2VwAyEA0rG1QbK6fGtkgsf7XXdbTFJVojK30tNEKrcjicKeVbI=",
        "lock": "Unlocked",
        "description": "Production vault for digital asset custody",
        "customProperties": {},
        "type": "v0_CreateVault"
      },
      "description": "Register production vault",
      "type": "Propose"
    },
    "signature": "<YOUR_SIGNATURE>"
  }'

For detailed instructions on vault registration, see Register vaults (UI) or Register vaults (API).

  1. Verify that the vault registered successfully:
    • Check in the UI under Administration > Vaults - you should see your vault listed
    • Or query via API: GET /v1/vaults/00000000-0000-0000-0000-000000000000

Step 8: Verify full installation

At this point, your installation is complete. Verify the system is working by creating a test blockchain account:

  1. Log in to the Web UI using the credentials from your Genesis payload.
  2. Navigate to Accounts > Create Account.
  3. Create a test account on one of your enabled blockchains (e.g., Bitcoin, Ethereum).
  4. If this creates the account successfully and you can see the blockchain address, you have verified your installation.

What this verifies:

  • Core services are running.
  • notary connects and signs transactions.
  • vault has registered and operates correctly.
  • Indexers connect to blockchain nodes.
  • End-to-end workflow (governance to vault to blockchain) is functional.

Genesis payload reference

This section provides detailed explanations of each section in the Genesis API payload.

Complete Genesis payload example

Complete genesis request body
{
    "rootDomainSetup": {
        "id": "e7eb7b63-d63b-4c30-9cca-d3b1ea7e46ad",
        "alias": "Root domain",
        "lock": "Unlocked",
        "governingStrategy": "CoerceDescendants",
        "permissions": {
            "readAccess": {
                "domains": ["operator"],
                "users": ["operator"],
                "endpoints": ["operator"],
                "policies": ["operator"],
                "accounts": ["operator"],
                "transactions": ["operator"],
                "requests": ["operator"],
                "events": ["operator"]
            }
        },
        "description": "A genesis payload for the creation of the root domain",
        "customProperties": {},
        "users": [
            {
                "id": "e1fe3431-f899-427b-9e67-a75fb9fe5e57",
                "alias": "operator@company.com",
                "publicKey": "MFkw...NfPfw/q9YW74Q==",
                "roles": ["manager"],
                "loginIds": [
                    {"id": "op1@company.com", "providerId": "harmonize"},
                    {"id": "op1@company.com", "providerId": "azure-ad"}
                ],
                "lock": "Unlocked",
                "description": "A manager user",
                "customProperties": {}
            }
        ],
        "policies": [
            {
                "id": "00000000-0000-0000-0000-000000000000",
                "alias": "Catch-All Policy",
                "rank": 20,
                "intentTypes": null,
                "scope": "Self",
                "scriptingEngine": "Javascript_v0",
                "condition": {
                    "expression": "context.request.author.id == 'e1fe3431-f899-427b-9e67-a75fb9fe5e57'",
                    "type": "Expression"
                },
                "workflow": [
                    {"role": "manager", "quorum": 1, "type": "RoleQuorum"}
                ],
                "lock": "Unlocked",
                "description": "Policy for exception handling",
                "customProperties": {}
            }
        ],
        "descendants": []
    },
    "cryptoSetup": {
        "apiSigning": "Secp256r1",
        "collectionSigning": "Secp256r1",
        "messageSigning": "Ed25519"
    },
    "tickers": [
        {
            "id": "688b48e3-8ce1-4e01-b242-96f03ecb554a",
            "ledgerId": "bitcoin-testnet",
            "kind": "Native",
            "name": "Bitcoin testnet",
            "decimals": 8,
            "symbol": "tBTC",
            "ledgerDetails": {"properties": {"type": "Native"}, "type": "Bitcoin"},
            "lock": "Unlocked",
            "description": null,
            "customProperties": {}
        }
    ],
    "systemProperties": [
        {
            "value": {
                "publicKey": {"value": "MFkw...HdVNyxTyl1plgAfh/Yg=="}
            },
            "type": "StateReviewAuthorityProperty"
        }
    ]
}

Root domain setup

This section is the only mandatory one. The rootDomainSetup section resembles a standard new domain creation payload. You specify the read permissions, users, and policies to create in the new Ripple Custody (Vault) instance.

For more information, see Create domains (UI) or Create domains (API).

Governing strategy

During domain creation, you choose between these governing strategies:

  • CoerceDescendants: The domain ignores the policies of its subdomains.
  • ConsiderDescendants: The domain considers the policies of its subdomains.

This field is optional. If you do not specify a governing strategy during domain creation, the domain defaults to a ConsiderDescendants strategy. Once created, you cannot change the governing strategy for the domain.

To execute a coercive intent within a locked domain, you must unlock the domain first.

Permissions

This access management section specifies the user roles that can view different entity types in the domain.

Users

This section is similar to a standard new user creation payload. Create enough users, with appropriate roles, to fulfill the defined policies.

Users follow these identifier guidelines:

  • Users within the same domain cannot have the same alias.
  • Users that have the same public key in different domains must have the same alias.
Example user with four roles and Azure AD SSO
"users": [
    {
        "id": "e1fe3431-f899-427b-9e67-a75fb9fe5e57",
        "alias": "operator@ripple.com",
        "publicKey": "MFkw...NfPfw/q9YW74Q==",
        "roles": ["manager","compliance","director","operator"],
        "loginIds": [
            {"id": "op1@company.com", "providerId": "harmonize"},
            {"id": "op1@company.com", "providerId": "azure-ad"}
        ],
        "lock": "Unlocked",
        "description": "A user",
        "customProperties": {}
    }
]

For a detailed description of the user creation payload, see Create a user (UI) or Create a user (API).

Policies

This section is similar to a standard new policy creation payload. Include a breakglass policy. For more information, see Breakglass policy.

For a detailed description of the policy creation payload, see Policies.

Descendants

This section is optional. Specify subdomains as an array of domain objects inside the descendants property. For each subdomain, specify the same information as for the root domain.

Crypto setup

This optional section specifies the cryptographic methods (elliptic curves) that the notary uses to create and verify signatures.

You can specify different elliptic curves for different operations:

  • apiSigning: The curve used to sign payloads returned to users following a GET request.
  • collectionSigning: The curve for signing the roots of all collections' Merkle trees. This guarantees tamper-proof storage of the information. For more information, see Security.
  • messageSigning: The curve used to sign internal messages from the notary to the vault.

For all types of operations you can choose between Secp256r1, Secp256k1, and Ed25519 elliptic curves.

Tickers

This optional section includes a list of tickers and other assets available for blockchain ledgers. Use this section to provide a list of pre-validated tickers.

System properties

This optional section registers the public key of the state review authority. Use a public key in a standard Ripple Custody (Vault) accepted format (secp256k1, secp256r1, or Ed25519). Represent it as a Base64-encoded string.

The state review authority acts as a trusted party to confirm the authenticity of the system state if you need to recover the anti-rewind file (ARF).

If you do not specify this information during Genesis, register it as soon as possible afterwards. This allows you to recover your system in a disaster recovery scenario. For more information, see Disaster recovery.

Genesis events

When you run the Genesis operation, Ripple Custody (Vault) includes two properties in the GenesisSucceeded event:

  • genesis: A data structure holding the notary-signed original Genesis payload.
  • createdEntitiesReferences: Key information on the data created.

For more information, see Event management > Genesis event.


(Reference) production.yaml vs. Genesis block

It is critical to understand the difference between the production.yaml file and the Genesis block.

  • The production.yaml file (Helm configuration):

    • Purpose: Defines infrastructure-level settings (the "plumbing").
    • Mutability: Mutable. You change this file to perform upgrades or update database passwords.
    • Controls: How the app runs (e.g., database.host, replicaCount, ingress.host).
    • When used: On every helm install or helm upgrade.
  • The Genesis block (Governance configuration):

    • Purpose: Defines immutable, application-level governance rules (the "constitution").
    • Mutability: Immutable. The system loads it once during the Genesis API call.
    • Controls: What the app does (e.g., initial admin users, root domain, policies).
    • When used: Loaded ONCE during first-time initialization.

For more details, see the Genesis payload reference section above.

(Post-install) Configuring secrets injection

For your initial deployment, you likely used standard Kubernetes secrets to store passwords (e.g., db-password-secret). For a hardened production environment, move these secrets into a dedicated external secrets manager.

Using Kubernetes Secrets

This is the default method, which you used during the initial installation.

  • How it works: You manually create a Kubernetes Opaque secret. The production.yaml file tells the application pods to mount this secret.

  • Example:

    # 1. Create the secret
    kubectl create secret generic db-password-secret \
      -n custody-core \
      --from-literal=password='my-super-secret-db-password'
    # 2. Configure production.yaml
    postgresql:
      external:
        existingSecret: "db-password-secret"
  • Limitation: Kubernetes stores the secret (base64 encoded, not encrypted at rest by default).

This method uses a tool like the External Secrets Operator (ESO) to fetch secrets from an external vault (e.g., HashiCorp Vault, Conjur) and inject them into your application.

  • How it works:
    1. Prerequisites: You must have an external secrets manager and have the External Secrets Operator installed in your cluster.

    2. Configure production.yaml: You tell the chart not to use existingSecret, but to instead get the secret from an external source.

      postgresql:
        external:
          # existingSecret: "db-password-secret"  # Disable this
          externalSecret:
            enabled: true  # Tells the chart to create an 'ExternalSecret' object
            secretStoreName: "hashicorp-vault-backend" \# \<-- You must create this 'SecretStore' object  
            remoteKey: "ripple-custody/db-password" \# \<-- Path to the secret in HashiCorp Vault  
            remoteProperty: "password" \# \<-- Key inside that secret
    3. Deploy: When you run helm upgrade, the chart creates an ExternalSecret object. The ESO detects this, connects to your vault, and fetches the secret. It then creates the final db-password-secret in Kubernetes for the application to use. This approach provides far more security because your hardened vault remains the "source of truth".