KAMAL_DESTINATION is unset while resolving aliases via load_raw_config
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
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
endhttps://github.com/basecamp/kamal/blob/v2.12.0/lib/kamal/configuration.rb#L20-L30
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))
endThor 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
# 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"# config/deploy.staging.yml
{}bin/kamal console -d staging
# ERB KAMAL_DESTINATION=nil
# destination missingbin/kamal app details -d staging
# ERB KAMAL_DESTINATION="staging"
# proceedsExpected
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:
def load_raw_config(config_file:, destination: nil)
ENV["KAMAL_DESTINATION"] = destination
load_config_files(config_file, *destination_config_file(config_file, destination))
endHappy 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
Source: basecamp/kamal