Overview
The External Secret Syncer (ESS) keeps Control Plane secrets in step with an external secret store. It polls each configured provider on an interval and creates or updates the matching Control Plane secret, so your workloads consume ordinary Control Plane secrets while the source of truth stays in HashiCorp Vault, AWS Secrets Manager, AWS Parameter Store, GCP Secret Manager, 1Password, 1Password Connect, Doppler or Infisical. Your provider connections and the list of secrets to sync live in async.yaml file that you store as an opaque secret before installing. It carries provider credentials, so it is never a Helm value. Every secret ESS writes is tagged syncer.cpln.io/source with the ESS workload, and ESS refuses to overwrite a secret tagged by a different ESS instance.
What Gets Created
configSecretName at /usr/src/app/sync.yaml. The identity carries no cloud-provider binding, so every provider authenticates with the credentials you put in sync.yaml.
Prerequisites
One opaque secret holding your entiresync.yaml must exist before you install. Secrets are org-level, so no GVC flag is involved.
Gather read-only provider credentials
Write your sync.yaml
Store it as an opaque secret
configSecretName to this name. Secret names are org-wide, so give each release its own. Read it back any time with:Installation
Once the secret exists, install from the marketplace registry with its name:UI
CLI
Terraform
Pulumi
Configuration
Image and Resources
resources sets the container limits. Change image only when moving to a newer ESS release.
Admin API Access
port is the ESS admin API, used by the /about health check. allowedIp lists the CIDRs that may reach the admin API on its public endpoint; the shipped 1.2.3.4 is a placeholder that admits nobody. Other workloads in the GVC cannot reach the admin API.
Sync Configuration Secret
configSecretName names the opaque secret holding your sync.yaml (see Prerequisites). The rest of this section is the schema of that file: a providers list and a secrets list.
Providers
Each provider has a uniquename and exactly one provider block. An optional syncInterval sets the default interval for every secret that uses it.
Secrets
Each entry insecrets writes one Control Plane secret. It needs a name (the Control Plane secret to create or update), a provider that matches a provider name, an optional syncInterval, and exactly one of the sync types below.
opaque stores one value as an opaque secret. Use the shorthand when you need only a path:
data key, so parse paths for Vault start with data.. With the shorthand and no default, a failed fetch fails the sync with no fallback.
dictionary stores several values as a dictionary secret. Each key is fetched independently, supports the same path, parse, default and encoding options, and a failure on one key does not block the others:
dictionaryFromProject syncs a whole provider project into one dictionary secret. Doppler takes a project/config path; GCP Secret Manager takes true and pulls every accessible secret from the provider’s project, one key per secret:
dictionaryFromJson fetches one value holding a JSON object and flattens it into a dictionary secret. It works with any provider:
db.host), arrays are stored as their JSON string, and null, numbers and booleans are stored as strings. A value that is not a JSON object is stored under a single __raw key, and ESS logs a warning.
discoverAllSecrets (GCP Secret Manager only, must be true) creates a separate Control Plane secret for every accessible secret in the provider’s project:
- A
cpln-type: dictionarylabel on the GCP secret flattens its JSON value into a dictionary secret (same rules asdictionaryFromJson);cpln-type: opaqueor no label stores an opaque secret. - Opaque values that are canonical base64 are marked
encoding: base64, so workloads receive the decoded value. Label the GCP secretcpln-encoding: disableto store a value verbatim, for example an API key that happens to look like base64. - Names are normalized to valid Control Plane names (lowercased, unsupported characters such as
_replaced by-, soMY_API_KEYbecomesmy-api-key). If two names collide, the last one wins and ESS logs a warning. - Each created secret carries a
syncer.cpln.io/discoveredBytag set to the entry’sname.
Path Formats
Sync Interval
Intervals use<hours>h<minutes>m<seconds>s; every part is optional but at least one is required (10s, 5m, 1h30m). The most specific setting wins: the secret’s syncInterval, then the provider’s, then the default of 300s.
Connecting
ESS has no client endpoint of its own. Workloads consume the secrets it writes by name, exactly like any other Control Plane secret, with no dependency on ESS being reachable.Operations
Updating the Sync Configuration
To add a provider, add a secret or replace a credential, export the secret, editsync.yaml in its payload, and apply it:
sync.yaml stops syncing it but leaves the Control Plane secret it created in place.
Upgrading From 2.0.x
Version 2.1.0 removes theessConfig values block. The schema is unchanged: migrating is a verbatim lift of that block into sync.yaml, minus the top-level essConfig: wrapper. Version 2.0.x does not create a GVC, so this is an ordinary in-place upgrade of the same release.
Move your essConfig block into sync.yaml
essConfig: wrapper, so providers: and secrets: become top-level keys.Create the secret under a new name
RELEASE_NAME-ess-config: that is the secret 2.0.x created, and the upgrade removes it.Upgrade with essConfig removed
essConfig from your values file, then upgrade:Rotate every credential that was in your values file
sync.yaml as described in Updating the Sync Configuration.Troubleshooting
The deployment never becomes ready and cpln logs is empty
The deployment never becomes ready and cpln logs is empty
configSecretName does not exist, so the container never starts.Fix: read status.versions[].message with cpln workload get-deployments RELEASE_NAME-ess --gvc GVC_NAME -o yaml, create the secret it names (see Prerequisites), and wait for the deployment to recover or force a redeployment.The syncer restarts with Error parsing sync config
The syncer restarts with Error parsing sync config
sync.yaml does not match the schema. Common messages are Secrets must only reference one secret type, Provider must have exactly one provider, Secrets must have a valid provider and Provider names must be unique.Fix: correct the file against Sync Configuration Secret, re-apply the secret and force a redeployment.A synced secret shows an error in syncer.cpln.io/lastError
A synced secret shows an error in syncer.cpln.io/lastError
parse on a value that is not JSON or YAML also fails.Fix: read the message from the tag with cpln secret reveal SYNCED_SECRET_NAME -o yaml, fix the path or credential in sync.yaml, and apply it. The tag clears on the next successful sync.Sync fails with Secret is managed by another ESS
Sync fails with Secret is managed by another ESS
syncer.cpln.io/source tag names a different ESS workload, for example a release installed earlier under another name.Fix: sync into a different secret name, or delete the existing secret so this ESS can create it.The admin API endpoint refuses connections
The admin API endpoint refuses connections
allowedIp still holds the 1.2.3.4 placeholder, or your address is not in it.Fix: set allowedIp to your CIDR and upgrade, or reach the API privately with cpln port-forward RELEASE_NAME-ess 3004:3004 --gvc GVC_NAME.Important Notes
- Create the
sync.yamlsecret before installing, and force a redeployment after every change to it. - Two ESS instances cannot manage the same secret; give each release its own target secret names.
- ESS never deletes a secret when its entry or upstream source is removed. Removing an entry from
sync.yaml, or deleting the source upstream, leaves the Control Plane secret in place; delete it yourself. - Changing a secret between
opaqueanddictionarymakes ESS delete and recreate it, so it briefly does not exist. - Version 2.1.0 removed
essConfig; follow Upgrading From 2.0.x before upgrading an older release.