Skip to content

Storage, database, and secrets

What to provision for object storage, persistent volumes, the database, and connector credentials before you create a runtime plane.

Not needed on Trust3 AI Cloud

A Trust3 AI Cloud plane runs in Trust3AICloud, so Trust3AI provides all of this. See Deployment Options.


What you need

Partially Managed (D2P) Self-Managed
Object storage Optional — only to keep audit logs in your own bucket Required
Persistent storage Required once you create connectors (AWS) Required
Database Not needed — Trust3AI runs it Required
Secret store Optional — defaults to Trust3's encrypted store Optional — same default
AI Features — Optional

On Partially Managed (D2P) all of this is optional to start with: credentials are encrypted in your browser and stored in the Trust3 database, and no bucket or external database is needed.


Object storage

Required on Self-Managed, where Trust3 stores runtime data and configuration. On Partially Managed (D2P) it is needed only if you want audit logs in your own bucket.

Create a dedicated S3 bucket in the same AWS region as your EKS cluster. Naming rules: 3–63 characters, lowercase letters, numbers, and hyphens only; globally unique across all AWS accounts.

Bash
1
2
3
aws s3api create-bucket \
  --bucket <bucket-name> \
  --region <region>

Keep server-side encryption (SSE-S3 or SSE-KMS) enabled and leave block-public-access enabled (the default). The IAM role from Cloud permissions must have S3 access to this bucket.

On Self-Managed the bucket name is permanent

The bucket name cannot be changed after setup.

Verify access
Bash
aws s3 ls s3://<bucket-name>

You need a storage account and a blob container inside it, both in the same Azure region as your AKS cluster.

Bash
1
2
3
4
5
az storage account create \
  --name <storage-account-name> \
  --resource-group <resource-group> \
  --location <location> \
  --sku Standard_LRS
Bash
1
2
3
az storage container create \
  --name <container-name> \
  --account-name <storage-account-name>

The managed identity from Cloud permissions needs the Storage Blob Data Contributor role on this container.

Create a bucket in the same GCP region as your GKE cluster. Naming rules: 3–63 characters — lowercase letters, numbers, hyphens, and underscores; globally unique across all GCP projects. Uniform bucket-level access is recommended; keep public access prevention enabled.

Bash
1
2
3
4
gcloud storage buckets create gs://<bucket-name> \
  --location=<region> \
  --uniform-bucket-level-access \
  --public-access-prevention

The service account from Cloud permissions needs Storage Object Admin on this bucket.

Verify access
Bash
gcloud storage ls gs://<bucket-name>

Persistent storage

AWS runtime planes use Amazon EBS for connector and system-app volumes. You choose the storage type when you create the plane, on both Partially Managed (D2P) and Self-Managed.

The storage choice is made once

Storage type is fixed when the plane is created and cannot be changed afterwards, because a Kubernetes StorageClass is immutable and volumes already bound never move to a different one. To move a plane to the other storage type, create a new plane.

Trust3 creates the StorageClass, so the only prerequisite is the EBS CSI driver on the cluster:

Bash
kubectl get csidriver ebs.csi.aws.com

Install the AWS EBS CSI Driver if it is missing. A missing driver shows up as a pod stuck in Pending with an unbound volume claim.

EBS volumes belong to one availability zone

A pod using an EBS volume can only run in that volume's zone. Keep node capacity available in every zone your volumes land in, using per-zone node groups or the cluster autoscaler's balance-similar-node-groups option. Otherwise a replaced node can leave the pod Pending with a volume node affinity conflict event.

Customer-managed encryption keys

Volumes are encrypted with your account's default EBS key. If that default is a customer-managed KMS key, the EBS CSI driver's IAM role needs permission to use it, or volume provisioning fails. A specific key cannot be chosen per plane: to control the key, use the Existing StorageClass option instead.

Choose EFS when a stateful pod must be able to restart in a different availability zone. EFS is regional, so it allows that, at the cost of slower per-volume performance and a file system you create and pay for per plane.

Create an EFS file system in the same VPC and region as your EKS cluster, with security groups allowing NFS traffic (port 2049) between the EKS nodes and the EFS mount targets. Install the AWS EFS CSI Driver.

Enable delete access point

Enable the delete access point option when installing the CSI driver, so the root volume created for a Persistent Volume in EFS is automatically deleted when you remove the PV/PVC from the cluster. Without it, deleted volumes leave data behind in EFS, increasing storage usage and costs.

Helm (values.yaml):

YAML
deleteAccessPointRootDir: true

YAML (controller args):

YAML
args:
- --delete-access-point-root-dir=true
Verify the driver and find your file system ID
Bash
kubectl get pods -A -l 'app.kubernetes.io/name=aws-efs-csi-driver'
Bash
kubectl get csidriver efs.csi.aws.com

Find your file system ID (fs-...):

Bash
aws efs describe-file-systems --query 'FileSystems[*].{Name:Name,Id:FileSystemId}' --output table

The EFS ID cannot be changed after setup.

Choose this when you need control that the StorageClass Trust3 creates does not give you: a specific KMS key, a different provisioner or volume type, or a cluster where the runtime must stay namespace-scoped.

The StorageClass must already exist on the cluster before you create the plane. Trust3 does not create one in this mode, and the runtime installs into its own namespace without cluster-scoped StorageClass permissions.

What the StorageClass must satisfy
Requirement Why
Supports ReadWriteOnce Trust3 volume claims request that access mode on AWS.
allowVolumeExpansion: true Required to grow an application volume later, along with a CSI driver that implements ControllerExpandVolume. An EFS-backed StorageClass cannot grow volumes at all, and the claim sits in ExternalExpanding indefinitely.
A reclaim policy you chose deliberately Delete removes the volumes when the plane is deleted. Retain leaves them, and their cost, behind.

For reference, the StorageClass Trust3 creates uses reclaimPolicy: Delete, volumeBindingMode: Immediate, and allowVolumeExpansion: true.

Confirm it exists, then enter its name in the wizard:

Bash
kubectl get sc <storageclass-name>

The name cannot be changed after the plane is created.


Database

Self-Managed only. Trust3 services need a relational database for metadata and policy storage.

Option Details Recommended for
Native Trust3 deploys an in-cluster database. No external database or secret needed. Data durability is tied to the cluster. Testing and staging
PostgreSQL Amazon Aurora PostgreSQL 16 or later, db.r6g.2xlarge (8 vCPU, 64 GB RAM) or larger, password authentication only. Production

Choosing Native needs nothing else here. For an external database, provision it before you start.

Find your DB subnet group and security group

You need a DB subnet group for the cluster VPC with at least two private subnets across different availability zones, and a security group allowing inbound 5432 (PostgreSQL) from the EKS node CIDR.

Bash
1
2
3
4
aws rds describe-db-subnet-groups \
  --query 'DBSubnetGroups[*].DBSubnetGroupName' \
  --output table \
  --region <region>
Bash
1
2
3
4
5
aws ec2 describe-security-groups \
  --filters Name=vpc-id,Values=<vpc-id> \
  --query 'SecurityGroups[*].[GroupId,GroupName]' \
  --output table \
  --region <region>
If you do not know your VPC ID

Find it from the EKS cluster:

Bash
1
2
3
4
aws eks describe-cluster \
  --name <cluster-name> \
  --query 'cluster.resourcesVpcConfig.vpcId' \
  --output text

Provision the cluster and writer instance

Set your values, then create the cluster:

Bash
REGION="<region>"
DB_PASSWORD="<password>"
SUBNET_GROUP="<subnet-group>"
SG_ID="<security-group-id>"

aws rds create-db-cluster \
  --db-cluster-identifier trust3-db-cluster \
  --engine aurora-postgresql \
  --engine-version 16.13 \
  --master-username admin \
  --master-user-password "$DB_PASSWORD" \
  --db-subnet-group-name "$SUBNET_GROUP" \
  --vpc-security-group-ids "$SG_ID" \
  --region "$REGION"

aws rds create-db-instance \
  --db-instance-identifier trust3-db-writer \
  --db-cluster-identifier trust3-db-cluster \
  --db-instance-class db.r6g.2xlarge \
  --engine aurora-postgresql \
  --no-publicly-accessible \
  --region "$REGION"

Wait until the cluster status shows available (about 10 minutes), then get the writer endpoint — this is your DB_HOST:

Bash
1
2
3
4
5
aws rds describe-db-clusters \
  --db-cluster-identifier trust3-db-cluster \
  --region "$REGION" \
  --query 'DBClusters[0].Endpoint' \
  --output text

Create the database

Connect to the instance and create the database:

Bash
psql -h "<db-host>" -p 5432 -U admin -W
SQL
CREATE DATABASE trust3_db;

Create the database secret

A secret holding the connection details as five keys — host, port, db, username, password — in whichever secret store you choose under Secret store.

Bash
1
2
3
4
5
6
7
8
9
kubectl create namespace <runtime-name>

kubectl create secret generic <secret-name> \
  -n <runtime-name> \
  --from-literal=host=<db-host> \
  --from-literal=port=<db-port> \
  --from-literal=db=trust3_db \
  --from-literal=username=<db-username> \
  --from-literal=password=<db-password>
Verify all five keys are present
Bash
1
2
3
kubectl get secret <secret-name> \
  --namespace <runtime-name> \
  -o jsonpath='{.data}' | jq 'keys'

Expected output: ["db","host","password","port","username"]

Bash
1
2
3
4
5
aws secretsmanager create-secret \
  --name <secret-name> \
  --description "External database credentials for Trust3 runtime" \
  --region <region> \
  --secret-string "{\"host\":\"<db-host>\",\"port\":\"<db-port>\",\"db\":\"trust3_db\",\"username\":\"<db-username>\",\"password\":\"<db-password>\"}"
Verify all five keys are present
Bash
1
2
3
4
aws secretsmanager get-secret-value \
  --secret-id <secret-name> \
  --region <region> \
  --query SecretString --output text | jq 'keys'

Expected output: ["db","host","password","port","username"]


Secret store

Where connector credentials live. The default needs no setup — credentials are encrypted in your browser with a per-runtime public key, and only your runtime can decrypt them with its private key.

No setup. Credentials are encrypted in your browser using a per-runtime public key and stored in the Trust3 database.

The private key is the only way back

If the runtime is deleted or its private key is lost, encrypted credentials cannot be recovered and must be re-entered.

A single secret in the runtime namespace; each connector credential is one key in its data map. If a value changes later, restart the affected connector from the portal.

Bash
1
2
3
4
kubectl create secret generic <runtime-name>-secrets \
  -n <runtime-name> \
  --from-literal=my-credential='dummy-value' \
  --dry-run=client -o yaml | kubectl apply -f -

A secret in the same region as your cluster; connector credentials are keys in its JSON payload. Access is granted through the IAM role in Cloud permissions.

Bash
1
2
3
4
aws secretsmanager create-secret \
  --name <secret-name> \
  --region <region> \
  --secret-string '{}'

Create a vault in the same region as your AKS cluster; each connector credential is an individual secret in the vault. The vault name must be globally unique (3–24 characters, alphanumeric and hyphens only). If a secret changes later, restart the affected connector from the portal.

Bash
1
2
3
4
az keyvault create \
  --name <key-vault-name> \
  --resource-group <resource-group> \
  --location <location>

The managed identity from Cloud permissions needs the Key Vault Secrets User role on the vault.

A secret in the same project as your GKE cluster. If a value changes later, restart the affected connector from the portal.

Bash
1
2
3
echo -n '{}' | gcloud secrets create <secret-name> \
  --data-file=- \
  --replication-policy=automatic

The service account from Cloud permissions needs Secret Manager Secret Accessor.

Self-Managed: one secret holds both

With Kubernetes Secrets or AWS Secrets Manager, the database connection keys must live in that same secret.


AI Features

Self-Managed only, and optional. Trust3 AI Features adds an AI assistant to the portal, served by an agent that runs on the runtime plane. It is turned off by default. Turn it on in the wizard's Storage & Connectivity step, under AI Features, or later from the runtime plane's configuration page after deployment is done.

Where the key lives

Trust3 AI Features uses your Anthropic API key. The key is never entered in the portal. Instead, it is stored in a secret under the data key anthropic_api_key. The Secret store you chose decides which secret that is:

Secret store Where the key goes
Kubernetes Secret or AWS Secrets Manager The secret already named there, next to the credentials stored in it. There is no separate secret name to enter.
Trust3 DB (End-To-End Encrypted) Enter a name in Anthropic API Key Secret Name. It defaults to <runtime-name>-secrets. Create it as a Kubernetes Secret in the runtime namespace.

The key is read once, at start-up

The secret does not have to exist when you save the configuration, but the AI Features service reads the key once, when it starts. Create the secret before you turn AI Features on. If you create it afterwards, or rotate the key, turn AI Features off and on again so the service picks up the new value.

Create the secret

Bash
SECRET_NAME='<secret-name>'
NAMESPACE='<runtime-name>'
ANTHROPIC_API_KEY='<YOUR_ANTHROPIC_API_KEY>'

kubectl create namespace "$NAMESPACE" || true

kubectl create secret generic "$SECRET_NAME" \
  --namespace "$NAMESPACE" \
  --from-literal=anthropic_api_key="$ANTHROPIC_API_KEY" \
|| kubectl patch secret "$SECRET_NAME" \
     --namespace "$NAMESPACE" \
     --type merge \
     -p "{\"stringData\":{\"anthropic_api_key\":\"$ANTHROPIC_API_KEY\"}}"

This creates the namespace if it does not exist, then creates the secret, or adds anthropic_api_key to it when the secret is already there.

The secret is a single JSON document that can already hold other credentials, such as the database connection keys, so the key is merged into it rather than written over it:

Bash
SECRET_NAME='<secret-name>'
REGION='<region>'
ANTHROPIC_API_KEY='<YOUR_ANTHROPIC_API_KEY>'

EXISTING=$(aws secretsmanager get-secret-value --secret-id "$SECRET_NAME" --region "$REGION" --query SecretString --output text 2>&1) || case "$EXISTING" in
  *ResourceNotFoundException*) EXISTING='{}' ;;
  *) printf '%s\n' "$EXISTING" >&2; EXISTING='' ;;
esac

MERGED=''
[ -n "$EXISTING" ] && MERGED=$(printf '%s' "$EXISTING" \
  | jq --arg key "$ANTHROPIC_API_KEY" '. + {anthropic_api_key: $key}')

if [ -z "$MERGED" ]; then
  echo "Could not read the current secret - nothing was written." >&2
else
  aws secretsmanager create-secret \
    --name "$SECRET_NAME" \
    --region "$REGION" \
    --secret-string "$MERGED" \
  || aws secretsmanager put-secret-value \
       --secret-id "$SECRET_NAME" \
       --region "$REGION" \
       --secret-string "$MERGED"
fi

If the secret cannot be read, nothing is written, so credentials already in it are never replaced by this key alone.

Model settings

Both optional:

Setting Details
LLM Model Anthropic model used by Trust3 AI Features. The field is pre-filled with claude-sonnet-5. If you clear it, Trust3 AI Features still uses claude-sonnet-5.
LLM Endpoint Leave blank to use Anthropic's public API. Set it to send requests through an LLM gateway instead, for example https://gateway.example/v1.