basecamp/kamal · error · RuntimeError

Could not read #{secrets} from Doppler

Error message

Could not read #{secrets} from Doppler

What it means

Raised in Kamal::Secrets::Adapters::Doppler#fetch_secrets when the backtick command `doppler secrets get <names> --json <flags>` exits non-zero. Login already succeeded (or was skipped via service token); this failure is about reading the requested secret names from the resolved Doppler project/config. The message interpolates the full requested list, including any `from` prefix.

Source

Thrown at lib/kamal/secrets/adapters/doppler.rb:26

      unless loggedin?
        `doppler login -y`
        raise RuntimeError, "Failed to login to Doppler" unless $?.success?
      end
    end

    def loggedin?
      `doppler me --json 2> /dev/null`
      $?.success?
    end

    def fetch_secrets(secrets, from:, **)
      secrets = prefixed_secrets(secrets, from: from)
      flags = secrets_get_flags(secrets)

      secret_names = secrets.collect { |s| s.split("/").last }

      items = `doppler secrets get #{secret_names.map(&:shellescape).join(" ")} --json #{flags}`
      raise RuntimeError, "Could not read #{secrets} from Doppler" unless $?.success?

      items = JSON.parse(items)

      items.transform_values { |value| value["computed"] }
    end

    def secrets_get_flags(secrets)
      unless service_token_set?
        project, config, _ = secrets.first.split("/")

        unless project && config
          raise RuntimeError, "Missing project or config from '--from=project/config' option"
        end

        project_and_config_flags = "-p #{project.shellescape} -c #{config.shellescape}"
      end
    end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Run the same command the adapter builds to see the real CLI error: `doppler secrets get <NAME> -p <project> -c <config> --json`.
  2. Verify every name in your kamal secrets list exists in that project/config (`doppler secrets list -p X -c Y`) and fix typos/renames in deploy.yml.
  3. Confirm --from matches the actual Doppler project/config slugs (not names) shown in the dashboard.
  4. If using DOPPLER_TOKEN, check the service token's project/config scope and workplace access.

Example fix

# before: deploy.yml requests a secret Doppler does not have
#   secrets:
#     - RAILS_MASTER_KEY   # absent in doppler project/blog/prod

# after: add it in Doppler (or remove from deploy.yml)
#   doppler secrets set RAILS_MASTER_KEY=... -p blog -c prod
kamal secrets pull
Defensive patterns

Strategy: try-catch

Validate before calling

require "open3"

def doppler_secrets_exist?(names, project:, config:)
  out, _err, status = Open3.capture3("doppler", "secrets", "get", *names, "-p", project, "-c", config, "--json")
  return false unless status.success?
  JSON.parse(out).keys.slice_when { true }.to_a.flatten # available names
  JSON.parse(out).key?(names.first)
rescue Errno::ENOENT
  false
end

Try / catch

begin
  secrets = adapter.fetch(%w[RAILS_MASTER_KEY], from: "blog/prod")
rescue RuntimeError => e
  if e.message.include?("Could not read") && e.message.include?("from Doppler")
    raise "Doppler fetch failed: verify each name exists in project/config — run `doppler secrets list -p blog -c prod`"
  end
  raise
end

Prevention

When it happens

Trigger: adapter.fetch(...) after successful auth when: one or more secret names do not exist in the project/config (doppler secrets get fails on unknown names); -p/-c flags derived from `--from=project/config` point at a nonexistent project or config; the service token lacks read scope for the project; Doppler API unreachable (network/proxy); or a mixed secrets list where the first entry has project/config but later entries resolve against a different config.

Common situations: Secret renamed or deleted in Doppler but still listed in deploy.yml secrets; --from default in deploy.yml pointing at an old project slug; personal token works locally but CI service token is scoped to another workplace; corporate proxy blocking api.doppler.com.

Related errors


AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21). Data as JSON: /api/errors/a3dc95e1cbb76323. Report an issue: GitHub.