basecamp/kamal · error · Kamal::ConfigurationError

accessories/#{name}: unknown role #{accessory_config["role"]

Error message

accessories/#{name}: unknown role #{accessory_config["role"]}

What it means

Kamal::Configuration::Accessory#ensure_valid_roles also handles the singular form: an accessory with `role:` (place this accessory on every host of that one role) is checked with config.role(name), and if no such role exists this Kamal::ConfigurationError is raised with the accessory name and the unknown role. It is the single-role counterpart to the plural `roles:` check.

Source

Thrown at lib/kamal/configuration/accessory.rb:277

    def extract_hosts_from_config_with_tag(tag)
      if (servers_with_roles = config.raw_config.servers).is_a?(Hash)
        servers_with_roles.flat_map do |role, servers_in_role|
          servers_in_role.filter_map do |host|
            host.keys.first if host.is_a?(Hash) && host.values.first.include?(tag)
          end
        end
      end
    end

    def network
      accessory_config["network"] || DEFAULT_NETWORK
    end

    def ensure_valid_roles
      if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
        raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
      elsif accessory_config["role"] && !config.role(accessory_config["role"])
        raise Kamal::ConfigurationError, "accessories/#{name}: unknown role #{accessory_config["role"]}"
      end
    end
end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Set `role:` to a key that exists under `servers:` (verify via `kamal config`).
  2. Or switch addressing modes: use `host: 1.2.3.4` or `hosts: [...]` if the accessory should run on specific machines rather than alongside a role.
  3. Grep the config for other references to the old name after any role rename.

Example fix

# config/deploy.yml — before
servers:
  web:
    - 1.2.3.4
accessories:
  mysql:
    role: db

# after
servers:
  web:
    - 1.2.3.4
accessories:
  mysql:
    host: 1.2.3.9
Defensive patterns

Strategy: validation

Validate before calling

def accessory_role_valid?(raw)
  role_names = raw["servers"].is_a?(Array) ? ["web"] : raw["servers"].keys.map(&:to_s)
  raw["accessories"].to_h.all? do |_name, cfg|
    cfg["role"].nil? || role_names.include?(cfg["role"].to_s)
  end
end

Try / catch

begin
  config = Kamal::Configuration.new(create_config_files: false)
rescue Kamal::ConfigurationError => e
  puts "Deploy config invalid: #{e.message}"
  exit 1
end

Prevention

When it happens

Trigger: An accessory declaring `role: db` when servers define only `web`/`workers`; a role rename that missed the accessory's `role:` key; singular `role:` pointing at a name that only exists as an accessory name, not a server role.

Common situations: Copy-paste of accessory blocks between projects; renaming roles in servers without grepping accessories; confusion between `role:` (must match a servers role) and `host:`/`hosts:` addressing.

Related errors


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