Storage backends
Moʻolelo's durable record store sits on object storage. You bring the bucket; Moʻolelo writes Apache Iceberg tables into it.
Supported backends
| Backend | URI scheme | Common deployments |
|---|---|---|
| AWS S3 | s3://, s3a://, s3n:// | Production AWS workloads |
| S3-compatible (MinIO, Ceph, R2, Backblaze) | s3:// | On-prem, hybrid, multi-cloud |
| Google Cloud Storage | gs:// | Production GCP workloads |
| Azure ADLS Gen2 | abfss:// | Production Azure workloads |
| Azure Blob Storage | wasbs:// | Legacy Azure workloads (prefer ADLS Gen2 for new deployments) |
| Local filesystem | file:// | Development only |
Switching between backends is a configuration change, not a code change. The same Moʻolelo binaries work with all of them.
Choosing a region
Pick a storage region in the same geographic area as your Moʻolelo deployment. Cross-region writes are slower and more expensive than same-region writes, and the iceberg-sink commits batches every few seconds — a slow round-trip directly affects ingest latency.
For multi-region deployments, each Moʻolelo region writes to its own bucket (typically named moolelo-warehouse-eu-central, moolelo-warehouse-us-east, etc.). Cross-region search is supported; cross-region writes are not (by design — see Data residency).
Configuring the iceberg-sink
The iceberg-sink reads two configuration values:
ICEBERG_SINK_WAREHOUSE— the warehouse URI; the scheme picks the backend.ICEBERG_SINK_CATALOG_PROPS_JSON— a JSON object of backend-specific credentials and endpoint settings.
In Helm, these come from chart values:
warehouse: s3://your-bucket/moolelo-warehouse/
catalogProperties:
literal: |
{
"s3.region": "us-east-1",
"s3.access-key-id": "AKIA…",
"s3.secret-access-key": "…"
}# Best practice: use IAM Roles for Service Accounts; no static keys.
warehouse: s3://your-bucket/moolelo-warehouse/
catalogProperties:
literal: |
{
"s3.region": "us-east-1"
}
# The pod's ServiceAccount has the IAM role attached;
# AWS SDK picks up credentials from the projected token.warehouse: s3://moolelo-warehouse/
catalogProperties:
literal: |
{
"s3.endpoint": "https://minio.your-domain.com",
"s3.region": "us-east-1",
"s3.access-key-id": "moolelo",
"s3.secret-access-key": "your-minio-password",
"s3.path-style-access": "true"
}warehouse: gs://your-bucket/moolelo-warehouse/
catalogProperties:
literal: |
{
"gcs.credtype": "adc"
}
# The pod's KSA is bound to a GCP service account with the
# right Storage permissions; ADC picks it up from the projected token.warehouse: gs://your-bucket/moolelo-warehouse/
catalogProperties:
literal: |
{
"gcs.keypath": "/var/run/secrets/gcp/sa.json"
}
# Mount the service-account JSON via a Kubernetes Secret + projected volume.warehouse: abfss://moolelo-warehouse@youraccount.dfs.core.windows.net/
catalogProperties:
literal: |
{
"adls.auth.shared-key.account.name": "youraccount",
"adls.auth.shared-key.account.key": "your-adls-key",
"adls.endpoint": "youraccount.dfs.core.windows.net",
"adls.protocol": "https"
}For production, mount the catalog properties JSON from a Kubernetes Secret rather than embedding it in the values file:
catalogProperties:
secretRef:
name: iceberg-catalog-credentials
key: props.jsonThe Secret's props.json key contains the JSON payload. With External Secrets Operator + Vault, the JSON gets refreshed automatically.
Configuring Nessie's catalog
Nessie also writes to the same warehouse — it manages the table metadata that lives alongside the Parquet files. Nessie's environment variables follow the same shape as Moʻolelo's, just with the prefix NESSIE_CATALOG_SERVICE_<BACKEND>_*. See the Project Nessie docs for the per-backend key list.
Bucket layout
Once Moʻolelo starts writing, your bucket will look like this:
moolelo-warehouse/
└── moolelo/
├── chunks/
│ ├── data/<parquet files>
│ └── metadata/<json snapshot files>
├── entities/
│ ├── data/...
│ └── metadata/...
├── relations/
└── acl_authority/The moolelo segment is the catalog namespace (configurable via ICEBERG_SINK_NAMESPACE). Each table holds its own data and metadata directories — standard Apache Iceberg layout.
Bucket policies
The Moʻolelo deployment needs:
- Read-write on the warehouse path (e.g.
moolelo-warehouse/moolelo/*). - Read on the bucket itself (for object listing).
A minimal IAM policy on AWS:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "MoʻoleloWarehouseAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-bucket",
"arn:aws:s3:::your-bucket/moolelo-warehouse/*"
]
}
]
}For GCS, grant the Storage Object Admin role on the bucket. For ADLS, the Storage Blob Data Contributor role.
Lifecycle policies
Iceberg snapshots accumulate over time. A reasonable default lifecycle policy on your bucket:
- Keep all current snapshots indefinitely.
- Delete orphaned files (files no longer referenced by any snapshot) older than 30 days.
Moʻolelo doesn't currently run snapshot expiration in-platform — that's on the roadmap. Until it ships, run expire_snapshots periodically via Trino or Spark against the catalog. The Apache Iceberg maintenance docs cover the procedure.
Encryption at rest
Use the bucket's native server-side encryption:
- AWS S3: SSE-S3 (default), SSE-KMS, or SSE-C.
- GCS: customer-managed encryption keys (CMEK).
- ADLS: customer-managed keys (CMK).
Moʻolelo doesn't add a layer of application-level encryption on top — the regional vault handles encryption of personally-identifiable fields, and the bucket-level encryption handles the rest.

