Cloud Secrets
Overview
Lifecycle integrates with cloud secret providers to securely inject secrets into your ephemeral environments. You can reference secrets directly in your lifecycle.yaml environment variables using a template syntax, and Lifecycle handles the rest.
Supported providers:
- AWS Secrets Manager
- GCP Secret Manager
Configuration
Secret providers are configured in the Lifecycle global_config database table under the secretProviders key.
Default Configuration
Both AWS and GCP providers are enabled by default. The ClusterSecretStore created by your platform team determines which provider actually works in your cluster.
- On AWS (EKS): Use
{{aws:path:key}}syntax. Theaws-secretsmanagerClusterSecretStore handles the request. - On GCP (GKE): Use
{{gcp:path:key}}syntax. Thegcp-secretmanagerClusterSecretStore handles the request.
If you reference a provider that doesn’t have a ClusterSecretStore in your cluster, the secret will fail to sync and a warning will be logged.
Configuration Fields
| Field | Required | Description |
|---|---|---|
enabled | Yes | Enable this provider |
clusterSecretStore | Yes | Name of the ClusterSecretStore CR configured by your platform team |
refreshInterval | Yes | How often ESO syncs secrets (e.g., 1h, 30m) |
allowedPrefixes | No | Restrict which secret paths can be referenced (empty = allow all) |
If you’re unsure which clusterSecretStore to use, check with your platform
team. They configure the External Secrets Operator and ClusterSecretStore as
part of cluster setup.
Syntax
Reference secrets using the following pattern:
| Component | Description | Example |
|---|---|---|
provider | Cloud provider (aws or gcp) | aws |
secret-path | Full path to the secret | myapp/database-credentials |
key | JSON key within the secret (optional) | password |
If your secret is a plaintext value (not JSON), omit the key portion: {"{{aws:myapp/api-key}}"}.
Basic Usage
JSON Secrets
For secrets stored as JSON objects, specify the key to extract:
If your AWS secret myapp/rds-credentials contains:
Your pod will receive:
DB_USERNAME=adminDB_PASSWORD=supersecretDB_HOST=db.example.com
Plaintext Secrets
For secrets stored as plain strings, omit the key:
Nested JSON
For nested JSON structures, use dot notation:
For a secret containing:
Where Secrets Work
The secret syntax works in all env blocks:
GitHub Services
Docker Services
Webhooks
Helm and Codefresh deployment types do not currently support cloud secrets.
Mixing Secrets with Template Variables
You can combine cloud secrets with regular template variables in the same env block:
Multiple Providers
Both providers are enabled by default. If your platform team has configured ClusterSecretStores for both AWS and GCP, you can reference secrets from either in the same deployment:
Each provider requires its own ClusterSecretStore. Contact your platform team if you need multi-cloud secret access.
Error Handling
Lifecycle uses a “warn and continue” approach for secret errors:
| Scenario | Behavior |
|---|---|
| Secret not found | Warning logged, env var not set, deployment continues |
| Key not found in JSON | Warning logged, env var not set |
| Provider not enabled | Warning logged, env var not set |
| Invalid syntax | Validation error at deploy time |
If a required secret is missing, your application may fail to start or behave unexpectedly. Always verify your secrets exist before deploying.
Best Practices
Use Descriptive Secret Paths
Organize secrets with meaningful paths:
Group Related Secrets
Store related credentials in a single JSON secret:
Then reference individual keys:
Avoid Secrets in Build Logs
Be cautious when using secrets in build-time environment variables, as they may appear in build logs. Prefer injecting secrets at runtime when possible.
Troubleshooting
Secret Not Injected
- Check the secret exists in AWS Secrets Manager / GCP Secret Manager
- Verify the path matches exactly (case-sensitive)
- Check the key name if using JSON secrets
- Review Lifecycle logs for warnings about missing secrets
- Verify provider is enabled in global configuration
Syntax Errors
Common syntax mistakes:
Permission Denied
If you see permission errors:
- Verify the External Secrets Operator has access to the secret path
- Check IAM policies allow access to the specific secret
- Contact your platform team if the secret is in a restricted path
Wrong Provider for Cluster
If you use {{gcp:...}} on an AWS cluster (or vice versa), the ExternalSecret will fail to sync because the ClusterSecretStore doesn’t exist. Check Lifecycle logs for warnings like “ClusterSecretStore not found”.
Prerequisites
For cloud secrets to work, your platform team must set up:
| Component | Description |
|---|---|
| External Secrets Operator | Kubernetes operator that syncs secrets from cloud providers |
| ClusterSecretStore | CR that configures ESO to connect to AWS Secrets Manager or GCP Secret Manager |
| IAM/Workload Identity | Permissions for ESO to read secrets (IRSA for AWS, Workload Identity for GCP) |
The clusterSecretStore value in your Lifecycle config must match the ClusterSecretStore name configured by your platform team.
Build-Time Secrets
The cloud secrets described above are runtime secrets — they get mounted into your running pods. Lifecycle also supports build-time secrets, which are injected during the Docker image build as build arguments.
You use the same {{provider:path:key}} syntax. The difference is where the secret is consumed: build-time secrets are available during docker build, while runtime secrets are mounted into the final running container.
Common use cases:
- Private NPM registry tokens (e.g.,
NPM_TOKENfor.npmrc) - Private package repository credentials
- License keys required during compilation
- Authentication tokens for private base images
Configuration
Define build-time secrets in the env block of your service’s docker configuration, just like runtime secrets:
Your Dockerfile then references them as build arguments:
How It Works
When you reference a {{provider:path:key}} value in a build service’s env block, Lifecycle resolves the secret from your cloud provider before the build starts. The resolved values are passed to Docker as build arguments (--build-arg), making them available as ARG variables in your Dockerfile.
Lifecycle waits for the secret to sync before starting the build, so you don’t need to worry about race conditions with the External Secrets Operator.
Both Buildkit and Kaniko build engines support build-time secrets. The secret resolution process is the same regardless of which engine you use.
Security Considerations
Build-time secrets are not masked in build logs. Any RUN command that
prints environment variables (e.g., echo $NPM_TOKEN, env, printenv) will
expose secret values in the build output. Build logs are fetched on-demand
from Kubernetes and are visible to anyone with access to the Lifecycle UI.
To minimize exposure:
- Never print environment variables during build steps
- Delete credential files after use (e.g.,
RUN rm -f .npmrcafternpm install) - Use multi-stage builds so secrets are only present in the builder stage, not the final image
- Prefer runtime secrets when possible — only use build-time secrets when the value is genuinely needed during image construction
Troubleshooting
Secret Not Available During Build
- Verify the syntax — make sure your env var uses the
{{provider:path:key}}pattern - Check that the secret exists in AWS Secrets Manager or GCP Secret Manager
- Confirm the ExternalSecret synced — Lifecycle waits for sync before building, but if the secret path is wrong or permissions are missing, the sync will fail
- Check build logs for warnings about missing or unresolved secrets
Secret Value Is Empty
If your secret is stored as a plaintext string (not JSON), you can omit the key. But if the secret is JSON, you must specify which key to extract.
Dockerfile Not Receiving the Value
Make sure your Dockerfile declares the build argument with ARG:
Related
- Template Variables - Learn about other template syntax
- Webhooks - Using secrets in webhook environment variables
- Native Builds - Build pipeline that consumes build-time secrets