Targets deploy secrets to their configured services via esk deploy. Each target is configured in the targets section of esk.yaml. Secrets declare which targets they deploy to — only targeted secrets are deployed.
For sync remotes (1Password, cloud files), see REMOTES.md.
| Target | Config key | External CLI | Deploy mode | Requires app? |
|---|---|---|---|---|
| .env file | .env |
None | Batch | Yes |
| AWS Lambda | aws_lambda |
aws |
Batch | No |
| AWS SSM | aws_ssm |
aws |
Individual | No |
| Azure App Service | azure_app_service |
az |
Individual | Yes |
| CircleCI | circleci |
circleci |
Individual | No |
| Cloudflare Workers | cloudflare |
wrangler |
Individual | Yes (Workers); No (Pages) |
| Convex | convex |
npx |
Individual | No |
| Docker Swarm | docker |
docker |
Individual | No |
| Fly.io | fly |
fly |
Individual | Yes |
| GCP Cloud Run | gcp_cloud_run |
gcloud |
Individual | Yes |
| GitHub Actions | github |
gh |
Individual | No |
| GitLab CI | gitlab |
glab |
Individual | No |
| Heroku | heroku |
heroku |
Individual | Yes |
| Kubernetes | kubernetes |
kubectl |
Batch | No |
| Netlify | netlify |
netlify |
Individual | No |
| Railway | railway |
railway |
Individual | No |
| Render | render |
curl |
Individual | Yes |
| Supabase | supabase |
supabase |
Individual | No |
| Vercel | vercel |
vercel |
Individual | No |
| Custom | User-defined | User-defined | Individual | No |
Deploy modes:
- Batch — When any secret changes for a target group, the entire output is regenerated. Used by .env file and Kubernetes targets.
- Individual — Each changed secret is deployed independently. Used by all other targets.
Generates .env files from the encrypted store. The output path is computed from a configurable pattern using the app path and environment.
- When any secret changes for an (app, environment) pair, the entire
.envfile for that pair is regenerated atomically (temp file + rename). - Secrets are grouped with
# === Group ===section headers and sorted alphabetically within each group. - The file includes a header comment with instructions for updating and regenerating.
- Parent directories are created automatically if they don't exist.
- Generated files are marked read-only to discourage manual edits (for example
0400on Unix). - Multiline values are rejected for
.envoutput safety.
targets:
.env:
pattern: "{app_path}/.env{env_suffix}.local"
env_suffix:
dev: ""
staging: ".staging"
prod: ".production"| Field | Required | Description |
|---|---|---|
pattern |
Yes | Path template for generated files. Supports {app_path} and {env_suffix} placeholders. |
env_suffix |
No | Map of environment name to suffix string. Environments not listed default to empty string. |
The pattern is resolved by replacing:
{app_path}with the app'spathfrom theappssection{env_suffix}with the value fromenv_suffixfor the current environment (or empty string if not mapped)
The result is relative to the project root (where esk.yaml lives).
Resolved paths must stay within the project root; traversal/symlink escape paths are rejected.
Examples with pattern: "{app_path}/.env{env_suffix}.local":
| App path | Environment | Suffix | Resolved path |
|---|---|---|---|
apps/web |
dev |
"" |
apps/web/.env.local |
apps/web |
prod |
".production" |
apps/web/.env.production.local |
apps/api |
staging |
".staging" |
apps/api/.env.staging.local |
Targets must include an app: app:environment.
secrets:
Stripe:
STRIPE_KEY:
targets:
.env: [web:dev, web:prod, api:dev]# Auto-generated by esk — do not edit manually
#
# Update secrets: esk set <KEY> --env <ENV>
# Regenerate file: esk deploy --env <ENV>
# === Convex ===
CONVEX_URL=https://example.convex.cloud
# === Stripe ===
STRIPE_KEY=sk_test_abc123
STRIPE_WEBHOOK_SECRET=whsec_xyzDeploys secrets as Lambda environment variables using the AWS CLI. Lambda's update-function-configuration API replaces the entire environment variable map atomically, so this target uses a read-merge-write pattern to preserve non-esk variables (NODE_ENV, AWS_REGION, etc.).
- Reads the current environment variables and
RevisionIdviaget-function-configuration. - Overlays esk secrets on top of existing variables.
- Writes the merged map via
update-function-configurationwith--cli-input-jsonpiped via stdin. - Uses
RevisionIdas an optimistic concurrency lock. Retries up to 2 times onResourceConflictException.
- AWS CLI installed and authenticated (
aws configure).
Preflight runs aws sts get-caller-identity to verify credentials and connectivity.
targets:
aws_lambda:
function_name:
dev: myapp-dev
prod: myapp-prod
region: us-east-1
profile: staging
kms_key_arn: "arn:aws:kms:us-east-1:123:key/abc"
env_flags:
prod: "--no-paginate"| Field | Required | Description |
|---|---|---|
function_name |
Yes | Maps esk environment names to Lambda function names. |
region |
No | AWS region. Passed as --region flag. |
profile |
No | AWS profile. Passed as --profile flag. |
kms_key_arn |
No | KMS key ARN for encrypting environment variables at rest. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Read current env vars:
aws lambda get-function-configuration --function-name <name> [--region ...] [--profile ...] [env_flags...]
# Write merged map (JSON via stdin):
echo '{"FunctionName":"...","Environment":{"Variables":{...}},"RevisionId":"..."}' | \
aws lambda update-function-configuration --cli-input-json file:///dev/stdin [--region ...] [--profile ...] [env_flags...]
# Delete (read-merge-write without the key):
aws lambda get-function-configuration ...
aws lambda update-function-configuration ...Targets are environment-only:
secrets:
General:
API_KEY:
targets:
aws_lambda: [dev, prod]Deploys secrets to AWS Systems Manager Parameter Store using aws ssm put-parameter. Values are sent via stdin (--cli-input-json file:///dev/stdin) to avoid exposing secrets in process listings.
- AWS CLI installed and authenticated (
aws configure).
Preflight runs aws sts get-caller-identity to verify credentials and connectivity.
targets:
aws_ssm:
path_prefix: "/{project}/{environment}/"
region: us-east-1
profile: staging
parameter_type: SecureString
env_flags:
prod: "--no-paginate"| Field | Required | Default | Description |
|---|---|---|---|
path_prefix |
Yes | — | Path prefix with {project} and {environment} interpolation. The key name is appended. |
region |
No | — | AWS region. Passed as --region flag. |
profile |
No | — | AWS profile. Passed as --profile flag. |
parameter_type |
No | SecureString |
SSM parameter type: SecureString, String, or StringList. |
env_flags |
No | — | Map of environment name to extra CLI flags appended to the command. |
The parameter name is built by replacing {project} and {environment} in path_prefix, then appending the key name.
Example with path_prefix: "/{project}/{environment}/":
| Project | Environment | Key | Parameter name |
|---|---|---|---|
myapp |
dev |
DB_PASS |
/myapp/dev/DB_PASS |
myapp |
prod |
API_KEY |
/myapp/prod/API_KEY |
# Put parameter (value via stdin as JSON):
echo '{"Name":"/myapp/dev/KEY","Value":"...","Type":"SecureString","Overwrite":true}' | \
aws ssm put-parameter --cli-input-json file:///dev/stdin [--region ...] [--profile ...] [env_flags...]
# Delete parameter:
aws ssm delete-parameter --name /myapp/dev/KEY [--region ...] [--profile ...] [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
aws_ssm: [dev, prod]Deploys app settings to Azure App Service web apps using az webapp config appsettings set.
Security note: Secret values are passed as CLI arguments (
--settings KEY=VALUE) and are visible in process listings (ps aux). The Azure CLI has no reliable stdin support for this command. A warning is printed at deploy time.
- Azure CLI (
az) installed and authenticated (az login).
Preflight runs az account show to verify the CLI is authenticated.
targets:
azure_app_service:
app_names:
web: my-azure-webapp
resource_group: my-resource-group
slot:
staging: staging
subscription: my-sub-id
env_flags:
prod: "--debug"| Field | Required | Description |
|---|---|---|
app_names |
Yes | Maps esk app names to Azure web app names. |
resource_group |
Yes | Azure resource group containing the web apps. |
slot |
No | Maps esk environment names to deployment slot names (e.g., staging). |
subscription |
No | Azure subscription ID. Passed as --subscription flag. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
az webapp config appsettings set --name <app> --resource-group <rg> --settings KEY=VALUE [--slot <slot>] [--subscription <sub>] [env_flags...]
az webapp config appsettings delete --name <app> --resource-group <rg> --setting-names KEY [--slot <slot>] [--subscription <sub>] [env_flags...]Targets must include an app: app:environment.
secrets:
General:
API_KEY:
targets:
azure_app_service: [web:dev, web:prod]Deploys context secrets to CircleCI using circleci context store-secret. Values are sent via stdin to avoid process argument exposure.
- CircleCI CLI installed.
Preflight verifies CLI installation.
targets:
circleci:
org_id: "00000000-0000-0000-0000-000000000000"
context_name: my-context
env_flags:
prod: "--some-flag value"| Field | Required | Description |
|---|---|---|
org_id |
Yes | CircleCI organization ID. |
context_name |
Yes | CircleCI context name where secrets are stored. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Value piped via stdin:
echo -n "<value>" | circleci context store-secret --org-id <org> <context> KEY [env_flags...]
# Delete:
circleci context remove-secret --org-id <org> <context> KEY [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
circleci: [dev, prod]Deploys secrets to Cloudflare Workers using wrangler secret put.
- For each secret, runs
wrangler secret put <KEY>with the value piped via stdin. - The command runs in the app's directory (so it picks up the local
wrangler.toml). - Per-environment flags (e.g.,
--env production) are appended to the command.
- Wrangler CLI installed and authenticated.
- A
wrangler.tomlin each app directory that uses this target.
targets:
cloudflare:
mode: workers # "workers" (default) or "pages"
pages_project: my-pages # required when mode is "pages"
env_flags:
dev: ""
prod: "--env production"| Field | Required | Default | Description |
|---|---|---|---|
mode |
No | workers |
Secrets API to use: workers (wrangler secret) or pages (wrangler pages secret). |
pages_project |
Conditional | — | Cloudflare Pages project name. Required when mode is pages. |
env_flags |
No | — | Map of environment name to extra CLI flags passed to Cloudflare secret commands (put/delete in workers or pages mode). Flags are split on whitespace and appended as separate arguments. |
# Workers mode (default) — in the app's directory:
echo "<value>" | wrangler secret put <KEY> [env_flags...]
wrangler secret delete <KEY> --force [env_flags...]
# Pages mode:
echo "<value>" | wrangler pages secret put <KEY> --project <pages_project> [env_flags...]
wrangler pages secret delete <KEY> --project <pages_project> --force [env_flags...]For a secret API_KEY targeting web:prod with env_flags.prod: "--env production":
cd apps/web && echo "sk_live_..." | wrangler secret put API_KEY --env productionWorkers mode: targets must include an app: app:environment.
Pages mode: targets are environment-only (no app prefix needed).
secrets:
Stripe:
STRIPE_KEY:
targets:
cloudflare: [web:prod]Deploys environment variables to Convex deployments using npx convex env set.
- For each secret, runs
npx convex env set <KEY>with the value piped via stdin in the configured Convex project directory. - If
deployment_sourceis set, readsCONVEX_DEPLOYMENTfrom that file and passes it as an environment variable — this tells the Convex CLI which deployment to target. - Per-environment flags (e.g.,
--prod) are appended to the command.
- Node.js and
npxavailable on PATH. - Convex project initialized in the configured path.
- Authenticated with Convex (e.g., via
npx convex login).
targets:
convex:
path: apps/api
deployment_source: apps/api/.env.local
env_flags:
dev: ""
prod: "--prod"| Field | Required | Description |
|---|---|---|
path |
Yes | Path to the Convex project directory (relative to project root). Commands run in this directory. |
deployment_source |
No | Path to a file containing a CONVEX_DEPLOYMENT=<value> line. The value is passed as an env var to the Convex CLI. Quotes around the value are stripped. |
env_flags |
No | Map of environment name to extra CLI flags. Flags are split on whitespace and appended as separate arguments. |
The deployment_source file is parsed line-by-line looking for CONVEX_DEPLOYMENT=<value>. This is typically the .env.local file generated by npx convex dev, which contains the deployment URL. All of the following formats are supported:
CONVEX_DEPLOYMENT=dev:my-app-123
CONVEX_DEPLOYMENT="dev:my-app-123"
CONVEX_DEPLOYMENT='dev:my-app-123'If the file doesn't exist or doesn't contain the variable, the command runs without the env var (Convex CLI will use its own default resolution).
# In the convex path, with CONVEX_DEPLOYMENT set (value piped via stdin):
CONVEX_DEPLOYMENT=dev:my-app-123 npx convex env set <KEY> [env_flags...]
# Delete:
CONVEX_DEPLOYMENT=dev:my-app-123 npx convex env unset <KEY> [env_flags...]Secret values are piped via stdin to npx convex env set.
Targets are environment-only (no app prefix needed since the target has its own path):
secrets:
Auth:
AUTH_SECRET:
targets:
convex: [dev, prod]Deploys secrets to Fly.io apps using fly secrets import. Values are piped via stdin to avoid exposing them in process listings.
Values containing newlines are rejected (the target sends KEY=VALUE over stdin, and newlines would inject additional variables).
- Fly CLI installed and authenticated (
fly auth login).
targets:
fly:
app_names:
web: my-fly-app
api: my-fly-api
env_flags:
prod: "--stage"| Field | Required | Description |
|---|---|---|
app_names |
Yes | Maps esk app names to Fly app names. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Value piped via stdin as KEY=VALUE:
echo "KEY=VALUE" | fly secrets import -a <fly-app> [env_flags...]
# Delete:
fly secrets unset KEY -a <fly-app> [env_flags...]Targets must include an app: app:environment.
secrets:
General:
API_KEY:
targets:
fly: [web:dev, web:prod]Deploys environment variables to GCP Cloud Run services using gcloud run services update.
Security note: Secret values are passed as CLI arguments (
--update-env-vars KEY=VALUE) and are visible in process listings (ps aux). The gcloud CLI has no stdin support for env var updates. A warning is printed at deploy time.
- Google Cloud CLI (
gcloud) installed and authenticated.
Preflight runs gcloud auth print-access-token --project <project> to verify authentication and project access.
targets:
gcp_cloud_run:
service_names:
web: my-web-service
api: my-api-service
project: my-gcp-project
region: us-central1
env_flags:
prod: "--project my-prod-project --region europe-west1"| Field | Required | Description |
|---|---|---|
service_names |
Yes | Maps esk app names to Cloud Run service names. |
project |
Yes | GCP project ID. |
region |
Yes | Cloud Run region (e.g., us-central1). |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
gcloud run services update <service> --update-env-vars KEY=VALUE --project <project> --region <region> [env_flags...]
gcloud run services update <service> --remove-env-vars KEY --project <project> --region <region> [env_flags...]Targets must include an app: app:environment.
secrets:
General:
API_KEY:
targets:
gcp_cloud_run: [web:dev, web:prod]Deploys environment variables to Netlify sites using netlify env:set.
Security note: Secret values are passed as CLI arguments and are visible in process listings (
ps aux). A warning is printed at deploy time.
- Netlify CLI installed (
npm install -g netlify-cli). - Site linked (
netlify link) sonetlify statussucceeds during preflight.
Preflight runs netlify status to verify CLI installation and site linkage.
targets:
netlify:
site: my-site-id # optional
env_flags:
prod: "--context production"| Field | Required | Description |
|---|---|---|
site |
No | Netlify site ID or name. Passed as --site flag if set. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
netlify env:set KEY VALUE [--site <site>] [env_flags...]
netlify env:unset KEY [--site <site>] [env_flags...]Targets are environment-only (no app prefix needed):
secrets:
General:
API_KEY:
targets:
netlify: [dev, prod]Deploys environment variables to Vercel projects using vercel env add with the value piped via stdin.
- Vercel CLI installed (
npm install -g vercel) and authenticated (vercel login).
targets:
vercel:
env_names:
dev: development
prod: production
env_flags:
prod: "--scope my-team"| Field | Required | Description |
|---|---|---|
env_names |
Yes | Maps esk environment names to Vercel environment names. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
echo "<value>" | vercel env add KEY <vercel-env> --force [env_flags...]
vercel env rm KEY <vercel-env> --yes [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
vercel: [dev, prod]Deploys repository secrets using gh secret set with the value piped via stdin to avoid exposing secrets in process listings.
- GitHub CLI installed and authenticated (
gh auth login).
targets:
github:
repo: owner/repo # optional — defaults to current repo
env_flags:
prod: "--env production"| Field | Required | Description |
|---|---|---|
repo |
No | GitHub repo in owner/repo format. Passed as -R flag if set. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
echo "<value>" | gh secret set KEY [-R <repo>] [env_flags...]
gh secret delete KEY [-R <repo>] [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
github: [dev, prod]Deploys config vars to Heroku apps using heroku config:set.
Security note: Secret values are passed as CLI arguments and are visible in process listings (
ps aux). A warning is printed at deploy time.
- Heroku CLI installed and authenticated (
heroku login).
targets:
heroku:
app_names:
web: my-heroku-app
env_flags:
prod: "--remote staging"| Field | Required | Description |
|---|---|---|
app_names |
Yes | Maps esk app names to Heroku app names. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
heroku config:set KEY=VALUE -a <heroku-app> [env_flags...]
heroku config:unset KEY -a <heroku-app> [env_flags...]Targets must include an app: app:environment.
secrets:
General:
API_KEY:
targets:
heroku: [web:dev, web:prod]Deploys secrets to Supabase edge functions using supabase secrets set. Values are piped via stdin to avoid exposing them in process listings.
Values containing newlines are rejected (the target sends KEY=VALUE over stdin, and newlines would inject additional variables).
- Supabase CLI installed.
Preflight runs supabase secrets list --project-ref <ref> to verify CLI installation and project accessibility.
targets:
supabase:
project_ref: abcdef123456
env_flags:
prod: "--experimental"| Field | Required | Description |
|---|---|---|
project_ref |
Yes | Supabase project reference ID. |
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Value piped via stdin as KEY=VALUE:
echo "KEY=VALUE" | supabase secrets set --project-ref <ref> [env_flags...]
# Delete:
supabase secrets unset KEY --project-ref <ref> [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
supabase: [dev, prod]Deploys environment variables to Railway projects using railway variables set with values piped via stdin (--stdin).
- Railway CLI installed and authenticated (
railway login).
Preflight runs railway whoami to verify CLI installation and authentication.
targets:
railway:
env_flags:
prod: "--environment production"| Field | Required | Description |
|---|---|---|
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Value piped via stdin:
railway variables set KEY --stdin [env_flags...]
railway variables delete KEY [env_flags...]Targets are environment-only:
secrets:
General:
API_KEY:
targets:
railway: [dev, prod]Deploys environment variables to Render services via the Render REST API. Unlike other targets, Render has no CLI — esk uses curl to make API calls.
API key and secret values are passed via curl --config - (stdin) to avoid exposing them in process argument lists.
curlinstalled (available on most systems).- A Render API key set in the
RENDER_API_KEYenvironment variable (or a custom env var name viaapi_key_env).
Preflight verifies curl is installed and the API key is valid by listing env vars for the first configured service.
targets:
render:
service_ids:
web: srv-abc123def456
api_key_env: RENDER_API_KEY
env_flags:
prod: "--proxy http://proxy:8080"| Field | Required | Default | Description |
|---|---|---|---|
service_ids |
Yes | — | Maps esk app names to Render service IDs. |
api_key_env |
No | RENDER_API_KEY |
Environment variable name holding the Render API key. |
env_flags |
No | — | Map of environment name to extra CLI flags appended to curl commands. |
# Deploy (PUT via curl --config stdin):
curl --config - --silent --fail-with-body [env_flags...]
# stdin contains: Authorization header, Content-Type, PUT method, URL, JSON body
# Delete (DELETE via curl --config stdin):
curl --config - --silent --fail-with-body [env_flags...]
# stdin contains: Authorization header, DELETE method, URLTargets must include an app: app:environment.
secrets:
General:
API_KEY:
targets:
render: [web:dev, web:prod]Deploys CI/CD variables to GitLab projects using glab variable set.
- GitLab CLI installed and authenticated (
glab auth login).
targets:
gitlab:
env_flags:
prod: "--masked"| Field | Required | Description |
|---|---|---|
env_flags |
No | Map of environment name to extra CLI flags appended to the command. |
# Value piped via stdin (not a positional argument):
echo -n "<value>" | glab variable set KEY --scope <env> [env_flags...]
# Delete:
glab variable delete KEY --scope <env> [env_flags...]Targets are environment-only (the environment name is used as the --scope value):
secrets:
General:
API_KEY:
targets:
gitlab: [dev, prod]Generates Kubernetes Secret manifests and applies them using kubectl apply. This is a batch target — when any secret changes, the entire Secret resource is regenerated and applied.
- Collects all secrets for the (environment) target group.
- Generates a YAML Secret manifest with base64-encoded values.
- Pipes the manifest to
kubectl apply -f -via stdin.
- kubectl installed and configured with access to the target cluster(s).
Preflight runs kubectl cluster-info to verify cluster connectivity.
targets:
kubernetes:
namespace:
dev: myapp-dev
prod: myapp-prod
secret_name: my-app-secrets
context:
prod: prod-cluster
env_flags:
prod: "--dry-run=client"| Field | Required | Default | Description |
|---|---|---|---|
namespace |
Yes | — | Maps esk environment names to Kubernetes namespaces. |
secret_name |
No | {project}-secrets |
Name of the Kubernetes Secret resource. |
context |
No | — | Maps esk environment names to kubectl contexts (--context flag). |
env_flags |
No | — | Map of environment name to extra CLI flags appended to the command. |
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
namespace: myapp-dev
type: Opaque
data:
DB_HOST: bG9jYWxob3N0 # base64("localhost")
DB_PASS: czNjcmV0 # base64("s3cret")echo "<manifest>" | kubectl apply -f - [--context <ctx>] [env_flags...]Targets are environment-only:
secrets:
General:
DB_HOST:
targets:
kubernetes: [dev, prod]Deploys secrets to Docker Swarm using docker secret create. Docker Swarm secrets are encrypted at rest in the Raft log and mounted as tmpfs files at /run/secrets/<name> inside containers — never exposed as environment variables or CLI arguments.
- Docker secrets are immutable — you cannot update a secret in place.
- On sync, esk removes the existing secret (
docker secret rm) then recreates it (docker secret create). The remove step tolerates "no such secret" errors for first-time creates. - Values are piped via stdin (
docker secret create <name> -) to avoid exposing them in process listings. - Services must be restarted to pick up new secret values regardless of the update method.
- If a secret is currently in use by a service, the remove will fail — this is a Docker constraint the user must resolve (e.g., by updating the service to remove the secret reference first).
- Docker installed with the daemon running.
- Swarm mode active (
docker swarm init).
Preflight runs docker info --format {{.Swarm.LocalNodeState}} and verifies the output is active.
targets:
docker:
name_pattern: "{project}-{environment}-{key}" # optional, this is the default
labels: # optional
managed-by: esk
env_flags: # optional
prod: "--context prod-swarm"| Field | Required | Default | Description |
|---|---|---|---|
name_pattern |
No | {project}-{environment}-{key} |
Name template for Docker secrets. Supports {project}, {environment}, and {key} placeholders. |
labels |
No | — | Static --label key=value flags applied to all created secrets. Useful for organizational tagging. |
env_flags |
No | — | Map of environment name to extra CLI flags appended to docker commands. |
Docker secrets are global within a swarm. The name_pattern prevents collisions across environments and projects.
Examples with name_pattern: "{project}-{environment}-{key}":
| Project | Environment | Key | Docker secret name |
|---|---|---|---|
myapp |
dev |
DATABASE_URL |
myapp-dev-DATABASE_URL |
myapp |
prod |
API_KEY |
myapp-prod-API_KEY |
# Remove existing (tolerates "no such secret"):
docker secret rm <name> [env_flags...]
# Create via stdin:
docker secret create [--label key=value ...] <name> - [env_flags...]
# Delete:
docker secret rm <name> [env_flags...]Targets are environment-only (Docker secrets are swarm-global, not scoped to an app):
secrets:
General:
API_KEY:
targets:
docker: [dev, prod]Define your own deploy targets by specifying commands directly in esk.yaml. Useful for services that esk doesn't have a built-in target for — internal APIs, niche platforms, or custom scripts.
Custom targets are individual-mode only (one secret at a time).
- On deploy, esk substitutes template variables in the command args and stdin, then executes the program.
- Non-zero exit codes are treated as deploy failures.
- If
preflightis configured, it runs before any deploys to verify the external service is reachable. - If
deleteis configured, it's called when pruning orphaned secrets. Otherwise, delete is a no-op.
Custom targets live under targets.custom as a named map. Secrets reference them by name, the same way they reference built-in targets.
targets:
custom:
my-api:
deploy:
program: curl
args: ["-X", "POST", "-d", "@-", "https://api.example.com/secrets/{{key}}"]
stdin: "{{value}}"
delete:
program: curl
args: ["-X", "DELETE", "https://api.example.com/secrets/{{key}}?env={{env}}"]
preflight:
program: curl
args: ["--fail", "-s", "https://api.example.com/health"]
env_flags:
prod: "--header X-Env:production"| Field | Required | Description |
|---|---|---|
deploy |
Yes | Command to run for each secret. Must have program and args. |
delete |
No | Command to run when pruning orphaned secrets. Same structure as deploy. |
preflight |
No | Command to run before any deploys to check service availability. Same structure. |
env_flags |
No | Map of environment name to extra CLI flags appended to deploy and delete commands. |
Each command block (deploy, delete, preflight) has:
| Field | Required | Description |
|---|---|---|
program |
Yes | Executable to run (must be in PATH). |
args |
Yes | List of arguments. Supports template variables. |
stdin |
No | String piped to the command's stdin. Supports templates. |
Variables are substituted in args and stdin at deploy time:
| Variable | Value |
|---|---|
{{key}} |
Secret name (e.g., API_KEY) |
{{value}} |
Secret value |
{{env}} |
Environment name (e.g., prod) |
{{app}} |
App name, or empty string if the secret has none |
Security note: Prefer
stdinfor{{value}}rather than putting it inargs. Values in args are visible in process listings (ps aux). esk warns at deploy time if{{value}}appears in deploy args.
- Names must contain only
a-z,A-Z,0-9,_,-. - Names cannot collide with built-in target names (
.env,cloudflare,convex,flyetc, see the full list at the top of this doc).
Targets are environment-only (no app prefix):
secrets:
General:
API_KEY:
targets:
my-api: [dev, prod]Internal API with token auth:
targets:
custom:
internal-api:
deploy:
program: curl
args:
[
"-X",
"PUT",
"-H",
"Authorization: Bearer $TOKEN",
"https://config.internal/secrets/{{key}}?env={{env}}",
]
stdin: "{{value}}"
delete:
program: curl
args:
[
"-X",
"DELETE",
"-H",
"Authorization: Bearer $TOKEN",
"https://config.internal/secrets/{{key}}?env={{env}}",
]
preflight:
program: curl
args: ["--fail", "-s", "https://config.internal/health"]Custom script wrapper:
targets:
custom:
my-vault:
deploy:
program: ./scripts/deploy-secret.sh
args: ["{{key}}", "{{env}}"]
stdin: "{{value}}"