amir20/dozzle · error
cloud: dial
Error message
cloud: dial: %w
What it means
unaryServiceClient creates the lazily-initialized gRPC client connection to the cloud service with grpc.NewClient and wraps any dial/client-construction failure as 'cloud: dial: %w'. Without this connection, both GetAlerts and SearchLogs cannot run. TLS credentials are chosen based on whether the target is insecure.
Solutions
- Check the cloud target/address configuration for typos and unsupported schemes
- Confirm the target format is valid for grpc.NewClient (e.g. dns:///host:port or host:port)
- Verify TLS expectations: if the endpoint is plaintext, the insecure path must be selected correctly
- Test endpoint reachability with a simple gRPC client or grpcurl before app startup
Example fix
// before
target := os.Getenv("DOZZLE_CLOUD_URL") // e.g. "https://cloud.example.com"
// after: strip scheme and use grpc-friendly target
target := strings.TrimPrefix(os.Getenv("DOZZLE_CLOUD_URL"), "https://")
conn, err := grpc.NewClient(target, creds) Defensive patterns
Strategy: validation
Validate before calling
target := strings.TrimSpace(cfg.CloudTarget)
if target == "" || strings.Contains(target, " ") || !strings.Contains(target, ":") {
return fmt.Errorf("invalid cloud target: %q", target)
} Try / catch
cl, err := unaryServiceClient(ctx)
if err != nil {
return nil, fmt.Errorf("cloud unavailable (%v), check DOZZLE_CLOUD target config", err)
} Prevention
- Validate the cloud target address format at startup
- Use grpcurl against the endpoint during deployment checks
- Strip http(s):// schemes before passing to grpc.NewClient
- Centralize target construction in one config function
When it happens
Trigger: grpc.NewClient returns an error for the configured target: malformed target/address string, invalid target scheme, or credentials setup failure when creating the connection.
Common situations: Misconfigured cloud endpoint URL (bad scheme or host), environment variable pointing at a wrong address, target string with stray whitespace or unsupported prefix.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07).
Data as JSON: /api/errors/bbea590dc10effd7.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cloud/search.go:65
// unaryServiceClient returns a (lazily dialed) reusable gRPC client. The
// underlying conn is shared across every Dozzle-initiated unary call so we pay
// the TLS handshake once per process — not once per keystroke or scroll.
func (c *Client) unaryServiceClient() (pb.CloudToolServiceClient, error) {
c.unaryConnMu.Lock()
defer c.unaryConnMu.Unlock()
if c.unaryClient != nil {
return c.unaryClient, nil
}
var creds grpc.DialOption
if c.plaintext {
creds = grpc.WithTransportCredentials(insecure.NewCredentials())
} else {
creds = grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, ""))
}
conn, err := grpc.NewClient(c.target, creds)
if err != nil {
return nil, fmt.Errorf("cloud: dial: %w", err)
}
c.unaryConn = conn
c.unaryClient = pb.NewCloudToolServiceClient(conn)
return c.unaryClient, nil
}
// SearchLogs runs a Cloud-side log search against the existing gRPC service.
// Reuses a long-lived gRPC conn (lazily dialed on first call) so the
// 500ms search timeout isn't burned on a TLS handshake per keystroke.
// Identity (user, instance) is enforced server-side from the authenticated
// metadata; this client passes only the per-request fields below.
func (c *Client) SearchLogs(ctx context.Context, query string, limit int32, hostID, containerID string, before int64) (*SearchLogResult, error) {
apiKey := c.apiKeyFunc()
if apiKey == "" {
return nil, ErrNotConfigured
}
client, err := c.unaryServiceClient()View on GitHub (pinned to d9463cbe21)