Operate

PostgreSQL Archive

anvil-agents uses PostgreSQL as its only durable AgentRun archive. Every installation mode resolves to one Kubernetes Secret key containing a PostgreSQL connection URI; the controller contract and archive schema do not change.

The historical table name anvilhub_agent_run_archives and archive status store value anvilhub-postgres remain stable for data compatibility. They do not create a dependency on Anvil Hub or Anvil Primaris.

Chart modes

Set one explicit archive.mode:

ModeDatabase ownershipController credential
disablednonenone
externaluser or platformreferenced Secret key
standalonechart renders one PostgreSQL StatefulSetexisting or chart-generated Secret
cloudnativepgchart renders one CloudNativePG ClusterCNPG-generated <cluster>-app Secret key uri

The chart never installs the CloudNativePG operator or its CRDs. An existing CNPG cluster that should remain independently managed is an external database; reference its application Secret instead of selecting cloudnativepg.

External PostgreSQL

Create or synchronize a same-namespace Secret whose selected key contains a complete PostgreSQL URI, then configure only its identity:

archive:
  mode: external
  external:
    databaseURLSecret:
      name: agent-archive-database
      key: url

This works with a manually created Secret, External Secrets, a managed database, or an existing CNPG application Secret. For the latter, set key: uri.

archive.databaseURLSecret.name and key remain as a deprecated compatibility alias. A non-empty legacy name selects external mode while archive.mode is left at its default. Do not configure the legacy and new external paths together.

Standalone PostgreSQL

The standalone mode creates a Service, a single-replica StatefulSet, and a persistent volume claim:

archive:
  mode: standalone
  standalone:
    auth:
      generate: true
    storage:
      size: 10Gi

For an ordinary live Helm installation, explicitly setting auth.generate=true generates an alphanumeric password on first install and reuses the retained Secret through Helm lookup on later upgrades. The Secret contains password and the full url; the URI is never a plain chart value.

Offline Helm rendering, including Argo CD's normal GitOps rendering, cannot reliably reuse lookup results. GitOps installations must provide archive.standalone.auth.existingSecret. That same Secret must contain the configured passwordKey and databaseURLKey, and both PostgreSQL and the controller consume it:

archive:
  mode: standalone
  standalone:
    auth:
      existingSecret: agent-archive-standalone
      passwordKey: password
      databaseURLKey: url

The database and username seed PostgreSQL only when an empty data directory is initialized. Changing those values does not migrate an existing volume. The official image makes POSTGRES_USER the bootstrap PostgreSQL superuser, so the standalone controller credential has database-superuser authority; this is another reason to use an externally managed database for production.

The default Alpine image runs as its postgres UID/GID 70 under a restricted container security profile. Adjust both standalone security contexts when selecting an image with a different UID.

Standalone mode is intended for development and small installations: it has no HA, TLS, backup, restore, or automated PostgreSQL upgrade workflow. Pin an appropriate image and own tested backups before relying on it for durable data.

The generated credential Secret and standalone PVC are marked for retention. A Helm uninstall removes the running StatefulSet and Service but leaves those data-bearing objects. Reinstall with the same release/name and settings to adopt them. Delete retained Secrets and PVCs only as a separate, deliberate data-destruction operation.

The storage class and access modes are immutable after PVC creation. Increasing the requested size depends on the StorageClass supporting expansion; decreases are not supported.

CloudNativePG

Select this mode only after the CloudNativePG operator and postgresql.cnpg.io/v1/Cluster CRD are installed:

archive:
  mode: cloudnativepg
  cloudnativePG:
    instances: 1
    storage:
      size: 10Gi

The installed operator must generate the application Secret key uri, as documented by CloudNativePG 1.19 and later. CRD discovery alone cannot prove that the operator is healthy or compatible.

The chart creates a small Cluster, bootstraps the anvil_agents database and application owner, disables superuser access, and reads the generated <cluster-name>-app Secret key uri. The application Secret is created asynchronously, so the controller Pod can remain pending briefly while CNPG initializes the cluster.

The Cluster is marked helm.sh/resource-policy: keep and Argo Prune=false,Delete=false; deleting it can cascade to CNPG-owned credentials and storage.

This convenience mode does not configure backups or HA by default. For a production CNPG design with its own lifecycle, backups, replication, or advanced PostgreSQL settings, manage the Cluster separately and use external mode.

Rotation and connectivity

The connection URI is loaded into the controller process at Pod start and its PostgreSQL pool is cached. After rotating an external or CNPG-managed Secret, change archive.restartToken to trigger a controller rollout.

For standalone mode, changing POSTGRES_PASSWORD in the Secret does not alter an already initialized database role. Credential rotation is manual database administration: change the role password in PostgreSQL, update both the Secret password and URI consistently, and then roll the controller. Plan recovery if those steps cannot be applied atomically.

External TLS parameters belong in the PostgreSQL URI. Mount required CA or client files with extraVolumes and extraVolumeMounts, and reference their container paths from the URI. The controller namespace and eligible nodes must be able to reach the database. Runner Pods do not connect to the archive. Standalone mode uses sslmode=disable and does not create a NetworkPolicy; namespace and network isolation remain the cluster operator's responsibility.

Run the real PostgreSQL migration/upsert integration check with Docker:

make archive-postgres-integration

Retention and verification

archive.terminalRetention controls deletion of terminal Kubernetes AgentRun objects; it is not a PostgreSQL row TTL. The chart rejects terminal retention when archive mode is disabled. The controller deletes a terminal AgentRun only after status records a successful archive result, timestamp, and digest. A database failure leaves the AgentRun present, records the archive error, and is retried.

Before enabling terminal retention, complete at least one run and verify its row directly:

SELECT namespace, name, phase, archived_at, digest
FROM anvilhub_agent_run_archives
ORDER BY archived_at DESC
LIMIT 10;

Archive rows have no automatic expiry in this release. Define database backup, monitoring, capacity, and row-retention policy outside the controller. The standalone API and CLI currently read live Kubernetes AgentRuns, not archived rows.

Switching modes does not copy data. Back up, restore or replicate the existing database, point the new mode at the migrated data, verify a real archive row, and only then enable terminal retention.