SuperAPI AI Directory

secrets

Deployment & operations

View repository

Updated 10-16 05:06

About this skill

Kamal Secrets

You are an expert in deploying applications with Kamal. Your goal is to help the user store deployment secrets safely, wire them into config/deploy.yml, and — when they use a password manager — pull them with the kamal secrets helpers instead of pasting plaintext.

In Kamal 2, secrets live in .kamal/secrets. Kamal uses dotenv to load that file automatically whenever a command needs the values, so there is no separate "generate secrets" step.

Start Here

Before asking the user questions, read what already exists:

  • .kamal/secrets (and .kamal/secrets-common, .kamal/secrets.<destination> if present) — what secrets are already defined and how they are sourced.
  • config/deploy.yml — the env block, especially which keys are listed under secret vs clear, and the registry block.
  • Check whether .kamal/secrets is git-ignored.

This tells you whether the user needs to add a new secret, move a plaintext value out of deploy.yml, switch to a vault helper, or set up per-environment secrets.

The .kamal/secrets File

.kamal/secrets is a dotenv file. Each line defines an environment variable that Kamal can reference. The right-hand side supports two kinds of substitution, evaluated on demand when you run a Kamal command:

Technique Syntax Use it for
Variable substitution NAME=$NAME Pulling a value already in your shell/CI environment
Command substitution NAME=$(command) Reading from a file or a CLI (e.g. a password manager)
# .kamal/secrets

# Pass a value through from the surrounding environment (e.g. set in CI)
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

# Read a value by running a command
RAILS_MASTER_KEY=$(cat config/master.key)

Do not commit real secret values. If you store secrets directly in .kamal/secrets, make sure the file is not checked into version control. Prefer variable substitution (from CI/your shell) or command substitution (from a password manager) so the file holds references, not plaintext.

KAMAL_REGISTRY_PASSWORD is the variable Kamal reads for registry authentication. For registry login itself, see the registry skill.

Referencing Secrets From deploy.yml

Defining a variable in .kamal/secrets does not, by itself, send it to your containers. You declare which variables are secret in the env block of config/deploy.yml.

List secret variable names under the secret key, and move any plain values under the clear key:

env:
  clear:
    DB_USER: app
  secret:
    - DB_PASSWORD

The names under secret must match variables defined in .kamal/secrets.

Behavior: Unlike clear values, secrets are not passed directly to the container. Kamal writes them to an env file on the host instead. For the full breakdown of clear, secret, and tags, see the env skill.

Aliased Secrets

When the environment variable name your app expects differs from the secret name, alias it with a : separator (ENV_NAME:SECRET_NAME). This is useful when the same ENV name needs different values in different contexts:

env:
  secret:
    - DB_PASSWORD:MAIN_DB_PASSWORD
  tags:
    secondary_db:
      secret:
        - DB_PASSWORD:SECONDARY_DB_PASSWORD
# .kamal/secrets
SECRETS=$(kamal secrets fetch ...)

MAIN_DB_PASSWORD=$(kamal secrets extract MAIN_DB_PASSWORD $SECRETS)
SECONDARY_DB_PASSWORD=$(kamal secrets extract SECONDARY_DB_PASSWORD $SECRETS)

Pulling Secrets From a Vault: kamal secrets

If the user keeps secrets in a password manager, use the kamal secrets helpers inside .kamal/secrets via command substitution. The helpers handle signing in, prompting for passwords, and efficiently fetching the secrets.

Subcommands

Command What it does
kamal secrets fetch [SECRETS...] --adapter=ADAPTER --account=ACCOUNT Fetch secrets from a vault
kamal secrets extract Extract a single secret from the results of a fetch call
kamal secrets print Print the secrets (for debugging)
kamal secrets help [COMMAND] Describe subcommands or one specific subcommand

Key options on fetch: --adapter (-a) selects the password manager, --account selects the account, and --from scopes where to look (vault, folder, item, or project) depending on the adapter.

The fetch → extract Pattern

Call fetch once to pull every secret in a single sign-in, then extract each value out of that result. This avoids re-authenticating for every variable:

# .kamal/secrets

SECRETS=$(kamal secrets fetch ...)

REGISTRY_PASSWORD=$(kamal secrets extract REGISTRY_PASSWORD $SECRETS)
DB_PASSWORD=$(kamal secrets extract DB_PASSWORD $SECRETS)

Walk-Through: 1Password

  1. Install and configure the 1Password CLI.
  2. Fetch with the 1password adapter, pointing --from at a Vault/Item:
# .kamal/secrets

SECRETS=$(kamal secrets fetch --adapter 1password --account myaccount \
  --from MyVault/MyItem REGISTRY_PASSWORD DB_PASSWORD)

REGISTRY_PASSWORD=$(kamal secrets extract REGISTRY_PASSWORD $SECRETS)
DB_PASSWORD=$(kamal secrets extract DB_PASSWORD $SECRETS)

extract accepts either the short secret name or a fully-qualified path, so all three of these resolve the same value:

kamal secrets extract REGISTRY_PASSWORD $SECRETS
kamal secrets extract MyItem/REGISTRY_PASSWORD $SECRETS
kamal secrets extract MyVault/MyItem/REGISTRY_PASSWORD $SECRETS

1Password: Interactive vs. Non-Interactive (CI) Auth

The op desktop-app integration authorizes reads transparently, but writes — creating a vault or item — prompt for Touch ID / approval and will time out in non-interactive, CI, or background contexts. For unattended deploys, authenticate with a service account instead of --account:

# .kamal/secrets
export OP_SERVICE_ACCOUNT_TOKEN=$(cat .kamal/OP_SERVICE_ACCOUNT_TOKEN)  # keep this file git-ignored

SECRETS=$(kamal secrets fetch --adapter 1password \
  --from MyVault/MyItem REGISTRY_PASSWORD DB_PASSWORD)

A service account only sees vaults explicitly shared with it — grant it read access to the deploy vault first, or fetch comes back empty.

Field references and extract: op item create "LABEL[password]=..." files the field under an auto-generated section, so its reference becomes op://MyVault/MyItem/Section_xxxx/LABEL. That still resolves — the adapter fetches by label=, and kamal secrets extract LABEL matches on the key suffix (/LABEL), so neither depends on the section name.

Supported Adapters

Kamal ships helpers for several password managers and secret stores:

Adapter Service Uses --account?
1password 1Password Yes
lastpass LastPass Yes (email)
bitwarden Bitwarden Yes (email)
bitwarden-sm Bitwarden Secrets Manager No (fetch all or ProjectID/all)
aws_secrets_manager AWS Secrets Manager Yes (AWS CLI profile, usually default)
doppler Doppler Ignored if given
gcp GCP Secret Manager Yes (gcloud account)
passbolt Passbolt Ignored if given

Each adapter has its own --from semantics (vaults, folders, items, projects, GCP project IDs) and prefix conventions. For per-adapter fetch examples and the full options reference, see references/vault-adapters.md.

Secrets For Multiple Environments

When you deploy to multiple destinations, split secrets across files. Kamal looks for the -common file first, then the environment-specific file, with later values overriding earlier ones:

Scenario Files read (in order)
No destination .kamal/secrets-common, then .kamal/secrets
Destination <dest> .kamal/secrets-common, then .kamal/secrets.<dest>

When a destination is selected, the plain .kamal/secrets file is not read. Put values shared across every destination in .kamal/secrets-common, and per-environment values in .kamal/secrets.<destination>.

Changing the Path: secrets_path

secrets_path sets the base path to your secrets files. It defaults to .kamal/secrets. Kamal applies the same -common / .<destination> lookup relative to whatever you set:

# config/deploy.yml
secrets_path: /user_home/kamal/secrets

Debugging

Use kamal secrets print to print the resolved secrets when something is not being picked up. Treat its output as sensitive — it reveals secret values.

kamal secrets print

Migrating File-Based Secrets Into a Vault

When secrets currently live in git-ignored files (or as plaintext in .kamal/secrets) and the user wants them in a password manager, move them without changing what gets deployed (the op commands below are 1Password; other managers use their own item-creation commands):

  1. Create the vault if it doesn't exist yet. Writes may need interactive approval (see the 1Password auth note above); a service-account or API token avoids the prompt.

    op vault create MyVault
    
  2. Create the item, loading each value straight from its file — so the plaintext never lands in your shell output, and trailing newlines are stripped exactly as $(cat file) already does:

    op item create --vault MyVault --title MyItem --category "Secure Note" \
      "REGISTRY_PASSWORD[password]=$(cat .kamal/REGISTRY_PASSWORD)" \
      "DB_PASSWORD[password]=$(cat .kamal/DB_PASSWORD)"
    
  3. Rewire .kamal/secrets to the fetch → extract pattern, and keep the old file-based lines commented out for a quick rollback.

  4. Verify without revealing values — fetch once, then compare checksums, not plaintext:

    SECRETS=$(kamal secrets fetch --adapter 1password --from MyVault/MyItem DB_PASSWORD)
    diff <(printf %s "$(kamal secrets extract DB_PASSWORD "$SECRETS")" | shasum) \
         <(printf %s "$(cat .kamal/DB_PASSWORD)" | shasum)
    
  5. Confirm the whole config resolves with kamal config (expect exit code 0), then delete the now-redundant files. Keep config/credentials/*.key if you still edit Rails credentials locally.

Migrating From Kamal 1

If the user is upgrading from Kamal 1, secrets have moved out of .env / .env.rb into .kamal/secrets:

  • kamal envify and kamal env have been removed. Secrets no longer have a separate lifecycle — substitution happens on demand. Replace envify with dotenv's variable and command substitution directly in .kamal/secrets.
  • .env is no longer auto-loaded into the environment, and values in .kamal/secrets are not loaded into the environment for use in deploy.yml either. If your deploy.yml referenced ENV[...] via ERB, load .env yourself:
# config/deploy.yml

<% require "dotenv"; Dotenv.load(".env") %>

servers:
  - <%= ENV["SERVER_IP"] %>

Related Skills

  • env: For setting plain (non-secret) env values, the clear / secret keys in detail, and tags.
  • registry: For registry login/logout and the KAMAL_REGISTRY_PASSWORD secret it consumes.
  • build: For build-time secrets and build args used while building your image.

Official docs: Secrets command · Secrets changes (upgrading) · Environment variables