basecamp/kamal · warning

Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}

Error message

Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}) in #{format("%0.2f", delay)}s: #{error.message}

What it means

Kamal patches SSHKit so SSH operations retry transient DNS failures: with_dns_retry rescues Resolv::ResolvError/ResolvTimeout or SocketError messages matching a DNS pattern, logs this warning, and sleeps with exponential backoff plus jitter before retrying. The message (sshkit_with_ext.rb:89) is a warning, not an exception — it tells you attempt N of config.dns_retries is about to be retried after a delay. The original error is re-raised only once attempts exceed the retry budget.

Source

Thrown at lib/kamal/sshkit_with_ext.rb:89

end

class SSHKit::Backend::Netssh
  module DnsRetriable
    DNS_RETRY_BASE = 0.1
    DNS_RETRY_MAX = 2.0
    DNS_RETRY_JITTER = 0.1
    DNS_ERROR_MESSAGE = /getaddrinfo|Temporary failure in name resolution|Name or service not known|nodename nor servname provided|No address associated|failed to look up|resolve/i

    def with_dns_retry(hostname, retries: config.dns_retries, base: DNS_RETRY_BASE, max_sleep: DNS_RETRY_MAX, jitter: DNS_RETRY_JITTER)
      attempts = 0
      begin
        attempts += 1
        yield
      rescue => error
        raise unless retryable_dns_error?(error) && attempts <= retries

        delay = dns_retry_sleep(attempts, base: base, jitter: jitter, max_sleep: max_sleep)
        SSHKit.config.output.warn("Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}) in #{format("%0.2f", delay)}s: #{error.message}")
        sleep delay
        retry
      end
    end

    private
      def retryable_dns_error?(error)
        case error
        when Resolv::ResolvError, Resolv::ResolvTimeout
          true
        when SocketError
          error.message =~ DNS_ERROR_MESSAGE
        else
          error.cause && retryable_dns_error?(error.cause)
        end
      end

      def dns_retry_sleep(attempt, base:, jitter:, max_sleep:)

View on GitHub (pinned to eee0083b38)

Solutions

  1. If occasional, no action: the built-in retry with backoff usually recovers and the deploy continues.
  2. Make resolution reliable: fix DNS records or /etc/hosts entries for the hosts, or reference hosts by IP in deploy.yml.
  3. Raise the budget: set `dns_retries: <n>` in the Kamal config if hosts are slow to become resolvable (boot lag, dynamic DNS).
  4. If resolution never succeeds, fix the resolver itself (systemd-resolved, VPN split-DNS, Docker DNS) — retries cannot cure a permanent failure once attempts exceed the limit.

Example fix

// before
# deploy.yml — SSH races slow DNS while hosts boot
servers:
  web:
    hosts: [app-1.example.com]

// after
dns_retries: 10
servers:
  web:
    hosts: [203.0.113.10]   # or pin IP / pre-warm DNS
Defensive patterns

Strategy: retry

Validate before calling

# Pre-resolve hosts before deploying
require 'resolv'
Resolv::DNS.open { |dns| dns.getaddress('app-1.example.com') } # raises if unresolvable

Try / catch

begin
  on(hosts) { execute :uptime }
rescue SocketError, Resolv::ResolvError => e
  # with_dns_retry already exhausted its budget — fix resolution, then re-run
  abort "DNS failed after retries: #{e.message}"
end

Prevention

When it happens

Trigger: Any SSH command to a hostname (deploy, app boot, healthchecks over SSH) while name resolution intermittently fails — getaddrinfo / 'Name or service not known' style errors — and retries remain: hosts or containers still booting, flaky VPN or systemd-resolved, Docker embedded DNS limits, or resolver timeouts under load in CI.

Common situations: Deploying immediately after spinning up hosts whose DNS records lag; internal DNS that is slow or rate-limited; network or VPN switching mid-deploy; parallel SSH connections stressing the local resolver.

Related errors


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