Skip to content

Data services

Three infrastructure blocks on an App claim are orthogonal to spec.type — a web app, worker, or cron can request any of them. Each is rendered by the App composition as a nested resource, not a separate claim you write yourself.

sqlInstance — PostgreSQL

Provisions a highly-available PostgreSQL cluster on CloudNativePG — the App composition renders a nested SQLInstance claim, which in turn renders the postgresql.cnpg.io/v1 Cluster (apis/sqlinstance/kcl/main.k:236-237 in Smana/crossplane-configuration):

  sqlInstance:
    enabled: true
    size: small               # small | medium | large
    storageSize: 20Gi
    instances: 2               # replicas for HA
    databases:
      - name: myapp
        owner: myapp-app
    roles:
      - name: myapp-app
        superuser: false
    backup:
      schedule: "0 2 * * *"    # if set, bucketName is required
      bucketName: myapp-db-backups
      retentionPolicy: "30d"

A backup schedule requires backup.bucketName — the API server enforces this. Schema migrations are declared via atlasSchema (a Git url, ref, and path to migration files); the composition renders a GitRepository + Kustomization + AtlasMigration pipeline per database so Atlas Operator applies them declaratively (apis/sqlinstance/kcl/main.k:477-539). See apps/AGENTS.md for the migration-repository layout and Git-ref-to-tag/branch rules if you use it.

Your workload’s DATABASE_URL is wired for you automatically — the App composition sets it to the CloudNativePG connection secret for the first declared database, unless you set DATABASE_URL yourself (apis/app/kcl/main.k:403-419 in Smana/crossplane-configuration).

kvStore — Valkey

An in-cluster Valkey key-value store for caching, sessions, or queues, delivered as a nested KVStore composition backed by the official valkey-helm chart (SPEC-012). Cache semantics: standalone and ephemeral by default — a restarted pod means a refilled cache, not lost data.

  kvStore:
    enabled: true
    size: small               # small | medium | large

Your workload automatically receives REDIS_URL=redis://<managed-name>-valkey:6379 unless you set REDIS_URL yourself (apis/app/kcl/main.k:420 in Smana/crossplane-configuration).

kvStore.type (valkey|redis) still exists on the schema but is ignored — the backend is Valkey-only. It’s kept for API compatibility with claims written before the migration off the legacy Bitnami chart.

objectStore — object storage with workload identity

Creates a bucket and a scoped workload identity so your pods get credentials automatically — no static keys, on either cloud. The same claim renders S3 on aws-0 and GCS on gcp-0; the composition picks the implementation from the cluster’s own EnvironmentConfig.

The bucket name is derived, not chosen — <scope>-ogenki-<app-name>, where the scope is the AWS region or the GCP project ID. The project ID is used on GCP because GCS bucket names are globally unique across all of Google Cloud, unlike S3 names.

  objectStore:
    enabled: true
    permissions: readwrite    # readwrite | readonly | custom
    versioning: true
    retentionDays: 90

Note there is no region. The claim does not say where the bucket lands — that comes from the cluster — which is what lets the same manifest deploy to either cloud. Override it only if you must, via aws.region or gcp.location.

aws-0gcp-0
BucketS3 Bucket + BucketVersioningGCS Bucket, versioning inline
IdentityEPI (EKS Pod Identity)GCPWorkloadIdentity
Grantinline IAM policy scoped to the bucket ARNroles/storage.objectAdmin on that one bucket

Both grants are bucket-scoped, not project- or account-wide. On GCP that matters more than it sounds: a project-level roles/storage.* would reach every bucket in the project, including OpenBao’s snapshots and database backups.

Two cloud asymmetries apply here: retentionDays currently takes effect on GCP only, and permissions: custom is AWS-only (requires aws.customPolicy; degraded to read-only on GCP) — details in the field reference.

See ADR-0002 for why Pod Identity over IRSA on AWS, ADR-0007 for why the cloud-specific knobs live in aws {} / gcp {} blocks, and EPI in the glossary for what the nested AWS identity claim renders.

Together: an app that uses all three

Toggling sqlInstance, kvStore, and objectStore on the same claim wires DATABASE_URL, REDIS_URL, the bucket, and its workload identity together automatically — you never type an xplane-* name or a secretKeyRef yourself. The App Wizard page walks a full worked example (Outline, a self-hosted wiki) that does exactly this.