{"record":{"id":"94f1d02f17b9714e","repo":"basecamp/kamal","slug":"local-registry-with-remote-builder-requires-an-ssh","errorCode":null,"errorMessage":"Local registry with remote builder requires an SSH URL (e.g., ssh://user@host)","messagePattern":"Local registry with remote builder requires an SSH URL \\(e\\.g\\., ssh://user@host\\)","errorType":"validation","errorClass":"Kamal::ConfigurationError","httpStatus":null,"severity":"error","filePath":"lib/kamal/configuration.rb","lineNumber":411,"sourceCode":"    def ensure_one_host_for_ssl_roles\n      roles.each(&:ensure_one_host_for_ssl)\n\n      true\n    end\n\n    def ensure_unique_hosts_for_ssl_roles\n      hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts }\n      duplicates = hosts.tally.filter_map { |host, count| host if count > 1 }\n\n      raise Kamal::ConfigurationError, \"Different roles can't share the same host for SSL: #{duplicates.join(\", \")}\" if duplicates.any?\n\n      true\n    end\n\n    def ensure_local_registry_remote_builder_has_ssh_url\n      if registry.local? && builder.remote?\n        unless URI(builder.remote).scheme == \"ssh\"\n          raise Kamal::ConfigurationError, \"Local registry with remote builder requires an SSH URL (e.g., ssh://user@host)\"\n        end\n      end\n\n      true\n    end\n\n    def ensure_no_conflicting_proxy_runs\n      all_hosts.each do |host|\n        run_configs = proxy_runs(host)\n        if run_configs.uniq.size > 1\n          raise Kamal::ConfigurationError, \"Conflicting proxy run configurations for host #{host}\"\n        end\n      end\n    end\n\n    def proxy_runs(host)\n      (host_roles(host) + host_accessories(host)).map(&:proxy).compact.map(&:run).compact\n    end","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/configuration.rb#L393-L429","documentation":"Kamal::Configuration#ensure_local_registry_remote_builder_has_ssh_url fires when the registry is local (registry configured with no server) while the builder is remote, and the remote builder URL's URI scheme is not ssh. A local registry is only reachable from machines on that network, so a remote builder must connect over SSH (e.g. ssh://user@host) for the push/pull to work; http/tcp URLs would point the builder at a registry it cannot reach or secure.","triggerScenarios":"A `registry:` block with no server (local registry) plus `builder: { remote: tcp://... }` or a plain hostname like `remote: buildhost.example.com` that parses to a nil scheme; switching registry to a local one but leaving an old docker-context-style remote builder URL; following a local-registry tutorial and pasting a bare hostname.","commonSituations":"Self-hosted registry on the deploy host with a remote build machine; migrating from docker context strings (which lack ssh://) to kamal 2 builder config; typos like `ssh//user@host` that parse to a nil scheme.","solutions":["Change the builder remote to an SSH URL: `builder: { remote: ssh://user@buildhost }`.","Ensure the deploying user has SSH key access to the build host (test with `ssh user@buildhost docker version`).","If the builder need not be remote, remove the `remote:` key so building happens locally against the local registry."],"exampleFix":"# config/deploy.yml — before\nregistry:\n  username: deploy\nbuilder:\n  remote: buildhost.example.com\n\n# after\nregistry:\n  username: deploy\nbuilder:\n  remote: ssh://deploy@buildhost.example.com","handlingStrategy":"validation","validationCode":"require \"uri\"\n\ndef local_registry_remote_builder_ok?(raw)\n  registry_local = raw.dig(\"registry\", \"server\").nil?\n  remote = raw.dig(\"builder\", \"remote\")\n  return true unless registry_local && remote\n  uri = URI.parse(remote.to_s)\n  uri.scheme == \"ssh\" || uri.scheme.nil? && remote.include?(\"@\") ? uri.scheme == \"ssh\" : false\nrescue URI::InvalidURIError\n  false\nend","typeGuard":null,"tryCatchPattern":"begin\n  config = Kamal::Configuration.new(create_config_files: false)\nrescue Kamal::ConfigurationError => e\n  puts \"Deploy config invalid: #{e.message}\"\n  exit 1\nend","preventionTips":["Always write remote builder URLs with an explicit scheme (ssh://user@host).","Test SSH access to the build host before wiring it into config.","Keep local-registry setups paired only with ssh:// remotes or local builders."],"tags":["kamal","registry","builder","ssh","configuration"],"backgroundTag":"invalid-url-scheme","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}