{"record":{"id":"c6fefb59ea11cb35","repo":"basecamp/kamal","slug":"retrying-dns-for-hostname-attempt-attempts","errorCode":null,"errorMessage":"Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}) in #{format(\"%0.2f\", delay)}s: #{error.message}","messagePattern":"Retrying DNS for #(.+?) \\(attempt #(.+?)/#(.+?)\\) in #(.+?)s: #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/kamal/sshkit_with_ext.rb","lineNumber":89,"sourceCode":"end\n\nclass SSHKit::Backend::Netssh\n  module DnsRetriable\n    DNS_RETRY_BASE = 0.1\n    DNS_RETRY_MAX = 2.0\n    DNS_RETRY_JITTER = 0.1\n    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\n\n    def with_dns_retry(hostname, retries: config.dns_retries, base: DNS_RETRY_BASE, max_sleep: DNS_RETRY_MAX, jitter: DNS_RETRY_JITTER)\n      attempts = 0\n      begin\n        attempts += 1\n        yield\n      rescue => error\n        raise unless retryable_dns_error?(error) && attempts <= retries\n\n        delay = dns_retry_sleep(attempts, base: base, jitter: jitter, max_sleep: max_sleep)\n        SSHKit.config.output.warn(\"Retrying DNS for #{hostname} (attempt #{attempts}/#{retries}) in #{format(\"%0.2f\", delay)}s: #{error.message}\")\n        sleep delay\n        retry\n      end\n    end\n\n    private\n      def retryable_dns_error?(error)\n        case error\n        when Resolv::ResolvError, Resolv::ResolvTimeout\n          true\n        when SocketError\n          error.message =~ DNS_ERROR_MESSAGE\n        else\n          error.cause && retryable_dns_error?(error.cause)\n        end\n      end\n\n      def dns_retry_sleep(attempt, base:, jitter:, max_sleep:)","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/sshkit_with_ext.rb#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If occasional, no action: the built-in retry with backoff usually recovers and the deploy continues.","Make resolution reliable: fix DNS records or /etc/hosts entries for the hosts, or reference hosts by IP in deploy.yml.","Raise the budget: set `dns_retries: <n>` in the Kamal config if hosts are slow to become resolvable (boot lag, dynamic DNS).","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."],"exampleFix":"// before\n# deploy.yml — SSH races slow DNS while hosts boot\nservers:\n  web:\n    hosts: [app-1.example.com]\n\n// after\ndns_retries: 10\nservers:\n  web:\n    hosts: [203.0.113.10]   # or pin IP / pre-warm DNS","handlingStrategy":"retry","validationCode":"# Pre-resolve hosts before deploying\nrequire 'resolv'\nResolv::DNS.open { |dns| dns.getaddress('app-1.example.com') } # raises if unresolvable","typeGuard":null,"tryCatchPattern":"begin\n  on(hosts) { execute :uptime }\nrescue SocketError, Resolv::ResolvError => e\n  # with_dns_retry already exhausted its budget — fix resolution, then re-run\n  abort \"DNS failed after retries: #{e.message}\"\nend","preventionTips":["Pre-warm DNS or use IPs for freshly booted hosts","Set dns_retries in the Kamal config sized to host boot time","Fix split-DNS, VPN, and systemd-resolved issues on the deploy box","Treat the warning as a signal: occasional retries are fine, constant retries mean a broken resolver"],"tags":["dns","ssh","kamal","retry","network"],"backgroundTag":"dns-resolution-failure","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}