#1953·kamal

KAMAL_DESTINATION is unset while resolving aliases via load_raw_config

Author: beetlegius-somosCreated Sep 12, 2026Updated Sep 12, 2026

Problem

Kamal::Configuration.create_from exports ENV["KAMAL_DESTINATION"] before rendering ERB, which is the documented way for config/deploy.yml to branch on destination (#1019, #998).

Alias commands never go through create_from on the first pass. They call Kamal::Configuration.load_raw_config instead, which renders the same ERB without setting the env var:

https://github.com/basecamp/kamal/blob/v2.12.0/lib/kamal/commander.rb#L132-L139

ruby
def resolve_alias(name)
  if @config
    @config.aliases[name]&.command
  else
    raw_config = Kamal::Configuration.load_raw_config(**@config_kwargs.to_h.slice(:config_file, :destination))
    raw_config[:aliases]&.dig(name)
  end
end

https://github.com/basecamp/kamal/blob/v2.12.0/lib/kamal/configuration.rb#L20-L30

ruby
def create_from(config_file:, destination: nil, version: nil)
  ENV["KAMAL_DESTINATION"] = destination
  raw_config = load_raw_config(config_file: config_file, destination: destination)
  new raw_config, destination: destination, version: version
end

def load_raw_config(config_file:, destination: nil)
  load_config_files(config_file, *destination_config_file(config_file, destination))
end

Thor does parse -d / --destination on the alias invocation (it ends up in options[:destination] and is forwarded as config_kwargs[:destination]). The destination file is therefore merged. Only the env var is missing during that first ERB render.

The real command then runs via Kamal::Cli::Main.start(...), which does use create_from, so a second render sees KAMAL_DESTINATION set. Anything that aborts or branches on the env var during the first pass never gets there.

This still reproduces on current master.

Reproduction

yaml
# config/deploy.yml
<% STDERR.puts "ERB KAMAL_DESTINATION=#{ENV["KAMAL_DESTINATION"].inspect}" %>
<% abort "destination missing" if ENV["KAMAL_DESTINATION"].to_s.empty? %>
service: demo
image: demo
servers:
  web:
    - 127.0.0.1
registry:
  server: example.com
  username: x
  password:
    - FOO
builder:
  arch: amd64
aliases:
  console: app exec --interactive --reuse "bin/rails console"
yaml
# config/deploy.staging.yml
{}
bin/kamal console -d staging
# ERB KAMAL_DESTINATION=nil
# destination missing
bin/kamal app details -d staging
# ERB KAMAL_DESTINATION="staging"
# proceeds

Expected

If -d staging is on the original command line, ENV["KAMAL_DESTINATION"] should already be "staging" the first time deploy.yml ERB runs, including when that load is only to expand an alias.

Suggested fix

Set the env var in load_raw_config (the shared path), not only in create_from:

ruby
def load_raw_config(config_file:, destination: nil)
  ENV["KAMAL_DESTINATION"] = destination
  load_config_files(config_file, *destination_config_file(config_file, destination))
end

Happy to open a PR if that looks right.

Workaround

Scan ARGV for -d / --destination before relying on ENV["KAMAL_DESTINATION"] in ERB. That is what #1019 was meant to remove.

Environment

  • kamal 2.12.0 (also current master)
  • Ruby 4.0.6