# 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](/products/custody/deployment/install/helm-chart-installation).)
* 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](/products/custody/deployment/install/helm-chart-installation) and have the Helm charts ready.
2. **Initial configuration:** Configure your own [`production.yaml`](/products/custody/deployment/reference) 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](/products/custody/governance/genesis/payload-reference) 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](/products/custody/deployment/install/helm-chart-installation), 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:

```bash
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](/products/custody/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:**


```yaml
# 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:

```bash
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:**

```bash
# 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](/products/custody/governance/genesis) and assemble the payload with [Genesis payload reference](/products/custody/governance/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.
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:**

```yaml
# 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](/products/custody/deployment/integrate-kms/on-premise-hsm/thales-luna) section.
2. Upgrade the Deployment to deploy the notary bridge and notary pods:

```bash
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:**

```bash
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](/products/custody/governance/genesis). For payload structure and examples, see [Genesis payload reference](/products/custody/governance/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:**

```yaml
# 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](/products/custody/deployment/integrate-kms/on-premise-hsm/thales-luna).
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](/products/custody/overview/architecture/unified-indexer-service#node-authentication-for-evm-chains) for full configuration reference.
**Example Ledger configuration:**

```yaml
# 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](/products/custody/deployment/reference/ledgers) and [Indexers configuration](/products/custody/deployment/reference/indexers).
3. Deploy vault and Indexers:

```bash
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:

```bash
# 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](/products/custody/deployment/integrate-kms/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:

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

```text
[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:


```bash
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](/products/custody/identity-and-access/vault-management/manage-vaults#register-a-vault-in-the-ui) or [Register a vault with the API](/products/custody/identity-and-access/vault-management/manage-vaults#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](/products/custody/deployment/reference) (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](/products/custody/governance/genesis) (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](/products/custody/governance/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:**

```bash
# 1. Create the secret
kubectl create secret generic db-password-secret \
  -n custody-core \
  --from-literal=password='my-super-secret-db-password'
```

```yaml
# 2. Configure production.yaml
postgresql:
  external:
    existingSecret: "db-password-secret"
```
* **Limitation:** Kubernetes stores the secret (base64 encoded, not encrypted at rest by default).


### Using an external secrets manager (recommended)

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.

```yaml
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".