basecamp/kamal · error · ArgumentError

No --hosts match for #{hosts.join(',')}

Error message

No --hosts match for #{hosts.join(',')}

What it means

Kamal::Commander#specific_hosts= is called when you pass the global --hosts filter (e.g. `kamal deploy --hosts 1.2.3.4,5.6.7.8`). It filters config.all_hosts with Kamal::Utils.filter_specific_items (fnmatch with FNM_EXTGLOB, so wildcards/extended globs are allowed). If none of the configured hosts match any filter, the result is empty and Kamal raises ArgumentError ('No --hosts match for ...').

Source

Thrown at lib/kamal/commander.rb:68

    else
      self.specific_hosts = [ config.primary_host ]
    end
  end

  def specific_roles=(role_names)
    @specifics = nil
    @specific_roles = if role_names.present?
      filtered = Kamal::Utils.filter_specific_items(role_names, config.roles)
      raise ArgumentError, "No --roles match for #{role_names.join(',')}" if filtered.empty?
      filtered
    end
  end

  def specific_hosts=(hosts)
    @specifics = nil
    @specific_hosts = if hosts.present?
      filtered = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
      raise ArgumentError, "No --hosts match for #{hosts.join(',')}" if filtered.empty?
      filtered
    end
  end

  def with_specific_hosts(hosts)
    original_hosts, self.specific_hosts = specific_hosts, hosts
    yield
  ensure
    self.specific_hosts = original_hosts
  end

  def accessory_names
    config.accessories&.collect(&:name) || []
  end

  def app(role: nil, host: nil)
    Kamal::Commands::App.new(config, role: role, host: host)
  end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Check the hosts defined in your config (`kamal app details` or read the servers: blocks in config/deploy*.yml) and pass exactly those names/IPs to --hosts.
  2. If using a destination (-d), remember host lists come from deploy.<destination>.yml — verify you filtered with values from that file.
  3. Use fnmatch-compatible patterns ('203.0.113.*') since regex alternation is not supported.
  4. If you meant to select by role, use --roles <role-name> instead.

Example fix

# before
# config/deploy.yml servers web: hosts: 203.0.113.10, 203.0.113.11
kamal deploy --hosts 10.0.0.10   # internal IP not in config
# after
kamal deploy --hosts 203.0.113.10
# or pattern-match a subnet:
kamal deploy --hosts '203.0.113.*'
Defensive patterns

Strategy: validation

Validate before calling

require "yaml"
config = YAML.load_file("config/deploy#{ENV["KAMAL_DESTINATION"] ? ".#{ENV["KAMAL_DESTINATION"]}" : ""}.yml")
hosts = config["servers"].values.flat_map { |r| Array(r["hosts"]) }
abort "host not in config" unless hosts.any? { |h| File.fnmatch(ARGV[0], h.to_s) }

Type guard

def hosts_match?(filters, configured_hosts)
  filters.any? { |f| configured_hosts.any? { |h| File.fnmatch(f, h.to_s, File::FNM_EXTGLOB) } }
end

Try / catch

begin
  Kamal::Commander.new.specific_hosts = %w[203.0.113.10]
rescue ArgumentError => e
  raise unless e.message.include?("--hosts")
  abort "check host names in deploy config: #{e.message}"
end

Prevention

When it happens

Trigger: `kamal deploy --hosts 10.0.0.9` when config lists only public hostnames; a glob matching no host (`--hosts 'db-*'`); typo'd or stale IPs after infrastructure changes; passing a role name instead of a hostname to --hosts; quoting that leaves brackets/commas attached to the filter string.

Common situations: Hosts changed in deploy.yml but runbooks/CI still pass old IPs; environments (destinations) with different host sets; SSH-config aliases that differ from the hosts in deploy.yml; copying a --roles example and using it for --hosts.

Related errors


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