basecamp/kamal · error · ArgumentError

Invalid builder configuration: the `docker` driver does not

Error message

Invalid builder configuration: the `docker` driver does not not support remote builders

What it means

Kamal::Configuration::Builder#valid? rejects combinations where `driver: docker` (the plain Docker builder rather than docker-container/buildx) is used together with `remote:`. The plain docker driver runs builds inside the local Docker daemon, so it cannot dispatch builds to a remote builder; Kamal raises this ArgumentError at configuration load to prevent a nonsensical build setup.

Source

Thrown at lib/kamal/configuration/builder.rb:162

  end

  def build_directory
    @build_directory ||=
      if git_clone?
        File.join clone_directory, repo_basename, repo_relative_pwd
      else
        "."
      end
  end

  def docker_driver?
    driver == "docker"
  end

  private
    def valid?
      if docker_driver?
        raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support remote builders" if remote
        raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support caching" if cached?
        raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support multiple arches" if arches.many?
      end

      if @options["cache"] && @options["cache"]["type"]
        raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
      end
    end

    def cache_image
      builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
    end

    def cache_image_ref
      [ server, cache_image ].compact.join("/")
    end

    def cache_options

View on GitHub (pinned to eee0083b38)

Solutions

  1. Remove the `remote:` key when using `driver: docker` — builds will run in the local Docker daemon.
  2. Or keep the remote builder but drop `driver: docker` so the default buildx/docker-container driver handles remote dispatch.
  3. If layering configs, audit the merged result (`kamal config`) to confirm the driver/remote pair is intentional.

Example fix

# config/deploy.yml — before
builder:
  driver: docker
  remote: ssh://buildhost

# after
builder:
  driver: docker
Defensive patterns

Strategy: validation

Validate before calling

def builder_driver_remote_compatible?(raw)
  b = raw["builder"] || {}
  !(b["driver"] == "docker" && b["remote"])
end

Type guard

def docker_driver_config?(builder_cfg)
  builder_cfg.is_a?(Hash) && builder_cfg["driver"] == "docker" &&
    !builder_cfg["remote"] && !builder_cfg.dig("cache", "type") &&
    Array(builder_cfg["arches"]).size <= 1
end

Try / catch

begin
  Kamal::Configuration.new(create_config_files: false)
rescue ArgumentError, Kamal::ConfigurationError => e
  abort "#{e.message}"
end

Prevention

When it happens

Trigger: `builder: { driver: docker, remote: ssh://user@buildhost }` in config/deploy.yml; keeping a remote: URL from a previous buildx setup after switching driver to docker; merging environment overrides that add remote while a base config pins driver: docker.

Common situations: Experimenting with the docker driver for speed (it avoids QEMU/build containers) while forgetting to strip remote builder config; layered configs (deploy.yml + deploy.production.yml) combining incompatible keys.

Related errors


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