Skip to content

Latest commit

 

History

History
1259 lines (914 loc) · 42.3 KB

File metadata and controls

1259 lines (914 loc) · 42.3 KB

Targets

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.

Overview

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.

.env file

Generates .env files from the encrypted store. The output path is computed from a configurable pattern using the app path and environment.

How it works

  1. When any secret changes for an (app, environment) pair, the entire .env file for that pair is regenerated atomically (temp file + rename).
  2. Secrets are grouped with # === Group === section headers and sorted alphabetically within each group.
  3. The file includes a header comment with instructions for updating and regenerating.
  4. Parent directories are created automatically if they don't exist.
  5. Generated files are marked read-only to discourage manual edits (for example 0400 on Unix).
  6. Multiline values are rejected for .env output safety.

Configuration

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.

Path resolution

The pattern is resolved by replacing:

  • {app_path} with the app's path from the apps section
  • {env_suffix} with the value from env_suffix for 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

Target format

Targets must include an app: app:environment.

secrets:
  Stripe:
    STRIPE_KEY:
      targets:
        .env: [web:dev, web:prod, api:dev]

Generated output

# 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_xyz

AWS Lambda

Deploys 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.).

How it works

  1. Reads the current environment variables and RevisionId via get-function-configuration.
  2. Overlays esk secrets on top of existing variables.
  3. Writes the merged map via update-function-configuration with --cli-input-json piped via stdin.
  4. Uses RevisionId as an optimistic concurrency lock. Retries up to 2 times on ResourceConflictException.

Prerequisites

  • AWS CLI installed and authenticated (aws configure).

Preflight runs aws sts get-caller-identity to verify credentials and connectivity.

Configuration

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.

Command executed

# 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 ...

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        aws_lambda: [dev, prod]

AWS SSM

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.

Prerequisites

  • AWS CLI installed and authenticated (aws configure).

Preflight runs aws sts get-caller-identity to verify credentials and connectivity.

Configuration

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.

Path resolution

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

Command executed

# 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...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        aws_ssm: [dev, prod]

Azure App Service

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.

Prerequisites

  • Azure CLI (az) installed and authenticated (az login).

Preflight runs az account show to verify the CLI is authenticated.

Configuration

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.

Command executed

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...]

Target format

Targets must include an app: app:environment.

secrets:
  General:
    API_KEY:
      targets:
        azure_app_service: [web:dev, web:prod]

CircleCI

Deploys context secrets to CircleCI using circleci context store-secret. Values are sent via stdin to avoid process argument exposure.

Prerequisites

Preflight verifies CLI installation.

Configuration

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.

Command executed

# 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...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        circleci: [dev, prod]

Cloudflare Workers

Deploys secrets to Cloudflare Workers using wrangler secret put.

How it works

  1. For each secret, runs wrangler secret put <KEY> with the value piped via stdin.
  2. The command runs in the app's directory (so it picks up the local wrangler.toml).
  3. Per-environment flags (e.g., --env production) are appended to the command.

Prerequisites

  • Wrangler CLI installed and authenticated.
  • A wrangler.toml in each app directory that uses this target.

Configuration

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.

Command executed

# 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 production

Target format

Workers 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]

Convex

Deploys environment variables to Convex deployments using npx convex env set.

How it works

  1. For each secret, runs npx convex env set <KEY> with the value piped via stdin in the configured Convex project directory.
  2. If deployment_source is set, reads CONVEX_DEPLOYMENT from that file and passes it as an environment variable — this tells the Convex CLI which deployment to target.
  3. Per-environment flags (e.g., --prod) are appended to the command.

Prerequisites

  • Node.js and npx available on PATH.
  • Convex project initialized in the configured path.
  • Authenticated with Convex (e.g., via npx convex login).

Configuration

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.

Deployment source

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).

Command executed

# 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.

Target format

Targets are environment-only (no app prefix needed since the target has its own path):

secrets:
  Auth:
    AUTH_SECRET:
      targets:
        convex: [dev, prod]

Fly.io

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).

Prerequisites

  • Fly CLI installed and authenticated (fly auth login).

Configuration

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.

Command executed

# 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...]

Target format

Targets must include an app: app:environment.

secrets:
  General:
    API_KEY:
      targets:
        fly: [web:dev, web:prod]

GCP Cloud Run

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.

Prerequisites

Preflight runs gcloud auth print-access-token --project <project> to verify authentication and project access.

Configuration

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.

Command executed

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...]

Target format

Targets must include an app: app:environment.

secrets:
  General:
    API_KEY:
      targets:
        gcp_cloud_run: [web:dev, web:prod]

Netlify

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.

Prerequisites

  • Netlify CLI installed (npm install -g netlify-cli).
  • Site linked (netlify link) so netlify status succeeds during preflight.

Preflight runs netlify status to verify CLI installation and site linkage.

Configuration

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.

Command executed

netlify env:set KEY VALUE [--site <site>] [env_flags...]
netlify env:unset KEY [--site <site>] [env_flags...]

Target format

Targets are environment-only (no app prefix needed):

secrets:
  General:
    API_KEY:
      targets:
        netlify: [dev, prod]

Vercel

Deploys environment variables to Vercel projects using vercel env add with the value piped via stdin.

Prerequisites

  • Vercel CLI installed (npm install -g vercel) and authenticated (vercel login).

Configuration

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.

Command executed

echo "<value>" | vercel env add KEY <vercel-env> --force [env_flags...]
vercel env rm KEY <vercel-env> --yes [env_flags...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        vercel: [dev, prod]

GitHub Actions

Deploys repository secrets using gh secret set with the value piped via stdin to avoid exposing secrets in process listings.

Prerequisites

  • GitHub CLI installed and authenticated (gh auth login).

Configuration

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.

Command executed

echo "<value>" | gh secret set KEY [-R <repo>] [env_flags...]
gh secret delete KEY [-R <repo>] [env_flags...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        github: [dev, prod]

Heroku

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.

Prerequisites

  • Heroku CLI installed and authenticated (heroku login).

Configuration

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.

Command executed

heroku config:set KEY=VALUE -a <heroku-app> [env_flags...]
heroku config:unset KEY -a <heroku-app> [env_flags...]

Target format

Targets must include an app: app:environment.

secrets:
  General:
    API_KEY:
      targets:
        heroku: [web:dev, web:prod]

Supabase

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).

Prerequisites

Preflight runs supabase secrets list --project-ref <ref> to verify CLI installation and project accessibility.

Configuration

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.

Command executed

# 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...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        supabase: [dev, prod]

Railway

Deploys environment variables to Railway projects using railway variables set with values piped via stdin (--stdin).

Prerequisites

  • Railway CLI installed and authenticated (railway login).

Preflight runs railway whoami to verify CLI installation and authentication.

Configuration

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.

Command executed

# Value piped via stdin:
railway variables set KEY --stdin [env_flags...]
railway variables delete KEY [env_flags...]

Target format

Targets are environment-only:

secrets:
  General:
    API_KEY:
      targets:
        railway: [dev, prod]

Render

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.

Prerequisites

  • curl installed (available on most systems).
  • A Render API key set in the RENDER_API_KEY environment variable (or a custom env var name via api_key_env).

Preflight verifies curl is installed and the API key is valid by listing env vars for the first configured service.

Configuration

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.

Command executed

# 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, URL

Target format

Targets must include an app: app:environment.

secrets:
  General:
    API_KEY:
      targets:
        render: [web:dev, web:prod]

GitLab CI

Deploys CI/CD variables to GitLab projects using glab variable set.

Prerequisites

  • GitLab CLI installed and authenticated (glab auth login).

Configuration

targets:
  gitlab:
    env_flags:
      prod: "--masked"
Field Required Description
env_flags No Map of environment name to extra CLI flags appended to the command.

Command executed

# 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...]

Target format

Targets are environment-only (the environment name is used as the --scope value):

secrets:
  General:
    API_KEY:
      targets:
        gitlab: [dev, prod]

Kubernetes

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.

How it works

  1. Collects all secrets for the (environment) target group.
  2. Generates a YAML Secret manifest with base64-encoded values.
  3. Pipes the manifest to kubectl apply -f - via stdin.

Prerequisites

  • kubectl installed and configured with access to the target cluster(s).

Preflight runs kubectl cluster-info to verify cluster connectivity.

Configuration

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.

Generated manifest

apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: myapp-dev
type: Opaque
data:
  DB_HOST: bG9jYWxob3N0 # base64("localhost")
  DB_PASS: czNjcmV0 # base64("s3cret")

Command executed

echo "<manifest>" | kubectl apply -f - [--context <ctx>] [env_flags...]

Target format

Targets are environment-only:

secrets:
  General:
    DB_HOST:
      targets:
        kubernetes: [dev, prod]

Docker Swarm

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.

How it works

  1. Docker secrets are immutable — you cannot update a secret in place.
  2. 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.
  3. Values are piped via stdin (docker secret create <name> -) to avoid exposing them in process listings.
  4. Services must be restarted to pick up new secret values regardless of the update method.
  5. 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).

Prerequisites

  • 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.

Configuration

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.

Name resolution

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

Command executed

# 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...]

Target format

Targets are environment-only (Docker secrets are swarm-global, not scoped to an app):

secrets:
  General:
    API_KEY:
      targets:
        docker: [dev, prod]

Custom

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).

How it works

  1. On deploy, esk substitutes template variables in the command args and stdin, then executes the program.
  2. Non-zero exit codes are treated as deploy failures.
  3. If preflight is configured, it runs before any deploys to verify the external service is reachable.
  4. If delete is configured, it's called when pruning orphaned secrets. Otherwise, delete is a no-op.

Configuration

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.

Template variables

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 stdin for {{value}} rather than putting it in args. Values in args are visible in process listings (ps aux). esk warns at deploy time if {{value}} appears in deploy args.

Naming rules

  • Names must contain only a-z, A-Z, 0-9, _, -.
  • Names cannot collide with built-in target names (.env, cloudflare, convex, fly etc, see the full list at the top of this doc).

Target format

Targets are environment-only (no app prefix):

secrets:
  General:
    API_KEY:
      targets:
        my-api: [dev, prod]

Examples

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}}"