dagger/dagger · error

checking for port %d/%s: %w

Error message

checking for port %d/%s: %w

What it means

Raised in portHealthChecker.Check after the exponential-backoff retry loop exhausts (or the context is cancelled) while trying to dial host:port inside the service's network namespace. It wraps the last dial error, meaning the container service port never became reachable within the backoff budget. Typically the wrapped error is connection refused, i/o timeout, or context deadline exceeded.

Source

Thrown at core/healthcheck.go:85

				// NB(vito): it's a _little_ silly to dial a UDP network to see that it's
				// up, since it'll be a false positive even if they're not listening yet,
				// but it at least checks that we're able to resolve the container address.
				conn, err := dialer.Dial(
					port.Protocol.Network(),
					net.JoinHostPort(d.host, fmt.Sprintf("%d", port.Port)),
				)
				if err != nil {
					slog.Warn("port not ready", "error", err, "elapsed", retry.GetElapsedTime())
					return "", err
				}

				endpoint := conn.RemoteAddr().String()
				_ = conn.Close()
				return endpoint, nil
			})
		}, backoff.WithContext(retry, ctx))
		if err != nil {
			return fmt.Errorf("checking for port %d/%s: %w", port.Port, port.Protocol.Network(), err)
		}

		slog.Info("port is healthy", "endpoint", endpoint)
	}

	return nil
}

type containerProcessExecutor interface {
	Exec(context.Context, string, executor.ProcessInfo) error
}

type dockerHealthcheck struct {
	args   []string
	origin trace.SpanContext
	ctr    *Container
	exec   containerProcessExecutor
	svcID  string

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Fix the server inside the container to bind 0.0.0.0 (not localhost) on the exposed port.
  2. Verify the exposed port number and protocol (tcp/udp) match what the app listens on.
  3. Check service logs for startup crashes; fix the startup failure or increase startup time.
  4. Set ExperimentalSkipHealthcheck(true) only if the port is UDP or intentionally uncheckable.
  5. If it's just slow startup, retry with a longer context deadline.

Example fix

// before
ctr.WithExposedPort(80).AsService().Up(ctx) // app listens on 127.0.0.1:80
// after
ctr.WithExposedPort(80).WithExec([], ContainerWithExecOpts{SkipEntrypoint: false}) // app binds 0.0.0.0:80
// or, for UDP services:
WithExposedPort(53, ContainerWithExposedPortOpts{Protocol: Udp, ExperimentalSkipHealthcheck: true})
Defensive patterns

Strategy: retry

Validate before calling

// verify the app binds on 0.0.0.0 and the port is correct
// e.g. inside the image: netstat -ltn | grep 0.0.0.0:8080

Type guard

var dialErr *net.OpError
if errors.As(err, &dialErr) && errors.Is(dialErr.Err, syscall.ECONNREFUSED) {
	// port closed: server not listening or wrong port
}

Try / catch

err := svc.Up(ctx)
if err != nil && strings.Contains(err.Error(), "checking for port") {
	var opErr *net.OpError
	if errors.As(err, &opErr) && errors.Is(opErr.Err, syscall.ECONNREFUSED) {
		return fmt.Errorf("service never listened on the exposed port; check bind address (0.0.0.0) and logs: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Starting a Dagger service (Container.up / service start) with a port whose ExperimentalSkipHealthcheck is false, where net.Dial to the container address fails for every retry until backoff gives up or ctx is done.

Common situations: The server inside the container crashed or never started, the app listens only on 127.0.0.1 instead of 0.0.0.0, wrong port mapping (container listens on 8080 but exposes 80), slow startup exceeding backoff limits, or cancelled context from an upstream timeout.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/46b1c4fe8a1631a4. Report an issue: GitHub.