basecamp/kamal · error · Kamal::ConfigurationError
Secret '#{key}' not found in #{secrets_files.join(", ")}
Error message
Secret '#{key}' not found in #{secrets_files.join(", ")} What it means
Kamal resolves ERb like <%= secrets.PASSWORD %> in deploy.yml through Kamal::Secrets, which Dotenv-parses .kamal/secrets-common and .kamal/secrets (or .kamal/secrets.<destination> when a destination is set). This Kamal::ConfigurationError means at least one secrets file exists, but none of them defines the requested key, so the lookup (secrets.fetch) raised KeyError and was converted.
Source
Thrown at lib/kamal/secrets.rb:16
require "dotenv"
class Kamal::Secrets
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
def initialize(destination: nil, secrets_path: ".kamal/secrets")
@destination = destination
@secrets_path = secrets_path
@mutex = Mutex.new
end
def [](key)
synchronized_fetch(key)
rescue KeyError
if secrets_files.present?
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
else
raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files (#{secrets_filenames.join(", ")}) provided"
end
end
def to_h
secrets
end
def secrets_files
@secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
end
def key?(key)
synchronized_fetch(key).present?
rescue KeyError
false
endView on GitHub (pinned to eee0083b38)
Solutions
- Fetch or append the missing key: kamal secrets fetch -a <adapter> --account <acct> PASSWORD >> .kamal/secrets
- Or add it manually as KEY=value in .kamal/secrets (same format as dotenv)
- Verify the exact key names with kamal secrets print and compare against every <%= secrets.X %> reference in deploy.yml
- If using destinations, confirm the key lives in .kamal/secrets-common or the .kamal/secrets.<destination> you deploy with
Example fix
# error: Secret 'PASSWORD' not found in .kamal/secrets-common, .kamal/secrets # fix (terminal) kamal secrets fetch -a bitwarden-sm PASSWORD >> .kamal/secrets # or (.kamal/secrets) # before: (no PASSWORD line) # after: PASSWORD=correct-horse-battery-staple
Defensive patterns
Strategy: validation
Validate before calling
# Verify every <%= secrets.X %> reference in deploy.yml resolves before deploying
require "yaml"
deploy = File.read("config/deploy.yml")
referenced = deploy.scan(/secrets\.([A-Za-z0-9_]+)/).flatten.uniq
files = [".kamal/secrets-common", ".kamal/secrets", ".kamal/secrets.#{ENV["KAMAL_DESTINATION"]}"].compact
existing = files.select { |f| File.exist?(f) }
existing.each do |f|
referenced -= File.readlines(f).map { |l| l[/\A([A-Za-z0-9_]+)=/, 1] }.compact
end
abort "unresolved secrets: #{referenced.join(", ")}" unless referenced.empty? Prevention
- Run `kamal secrets print` after every fetch to confirm key names match deploy.yml exactly
- Add the referenced-vs-defined check above as a CI step so a new <%= secrets.X %> without a fetch fails the build
- Keep shared keys in .kamal/secrets-common and destination-only keys in .kamal/secrets.<destination>
When it happens
Trigger: deploy.yml references secrets.PASSWORD but no line PASSWORD=... exists in .kamal/secrets-common or the destination-specific file; the key was fetched under a different name; the key exists only in a different destination's file (only common + current destination are read).
Common situations: Adding a new env/secret reference to deploy.yml before running kamal secrets fetch; typo between deploy.yml and the secrets file; fetching secrets while a destination (-d staging) is active so they land in .kamal/secrets.staging but deploying without the destination.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- Secret '#{key}' not found, no secret files (#{secrets_filena
- SSL is only supported on a single server unless you provide
- You must specify a destination
- Missing required configuration for #{key}
- Missing required configuration for image
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/468b44e16395e7ac.
Report an issue: GitHub.