Skip to content

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 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 the one-time Genesis API call to initialize the first governed state and 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. For configuration reference pages, see Deployment reference.

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: openid.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.

Before running this step, review Design your genesis governance model and assemble the payload with Genesis payload reference.

  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 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

    Use the finalized genesis-payload.json from your genesis governance design. For payload structure and examples, see Genesis payload reference.

    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.

    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
    • Nodes requiring token-based auth (e.g., Besu nodes that issue short-lived JWTs): Use the customJwt or header authentication mode in the UIS indexer.node.authentication block. This automatically deploys a Node Gateway sidecar alongside the EVM indexer pod — no additional Helm flag is needed. See Node authentication for EVM chains for full configuration reference.

    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 and Indexers configuration.

  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. 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 a vault in the UI or Register a vault with the 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.

(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 Genesis payload reference.

(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".