Skip to content

Commit 4273270

Browse files
barckcodeclaude
andcommitted
fix: use Docker HEALTHCHECK status for Ollama readiness polling
The direct HTTP polling approach failed because the API container and Ollama container are on different Docker networks and cannot reach each other by IP. Switch back to polling Docker HEALTHCHECK status, which now uses "ollama list" (available in the image) instead of curl. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 69422f2 commit 4273270

1 file changed

Lines changed: 14 additions & 40 deletions

File tree

internal/runtime/ollama.go

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"bufio"
55
"context"
66
"fmt"
7-
"io"
87
"log/slog"
9-
"net/http"
108
"os/exec"
119
"strings"
1210
"time"
@@ -129,53 +127,29 @@ func (d *DockerRuntime) EnsureOllama(ctx context.Context) (string, error) {
129127
return resp.ID, nil
130128
}
131129

132-
// waitForOllamaHealthy polls the Ollama HTTP API directly until it responds
133-
// or the timeout expires. This is more reliable than Docker HEALTHCHECK because
134-
// it doesn't depend on tools being installed inside the container.
130+
// waitForOllamaHealthy polls the Docker HEALTHCHECK status until the container
131+
// becomes healthy or the timeout expires. The HEALTHCHECK uses "ollama list"
132+
// which runs inside the container, avoiding network reachability issues between
133+
// the API container and the Ollama container (they may be on different Docker networks).
135134
func (d *DockerRuntime) waitForOllamaHealthy(ctx context.Context, containerID string, timeout time.Duration) error {
136135
deadline := time.Now().Add(timeout)
137-
httpClient := &http.Client{Timeout: 3 * time.Second}
138-
139136
for time.Now().Before(deadline) {
140-
// Get container IP to call the Ollama API directly.
141137
info, err := d.client.ContainerInspect(ctx, containerID)
142138
if err != nil {
143139
return fmt.Errorf("inspecting ollama container: %w", err)
144140
}
145-
146-
// Try all available network IPs (container may not have a default bridge IP).
147-
var ollamaURL string
148-
if info.NetworkSettings != nil {
149-
// First try the default IP.
150-
if info.NetworkSettings.IPAddress != "" {
151-
ollamaURL = fmt.Sprintf("http://%s:%s/api/tags", info.NetworkSettings.IPAddress, OllamaInternalPort)
152-
}
153-
// If no default IP, try IPs from named networks.
154-
if ollamaURL == "" {
155-
for _, net := range info.NetworkSettings.Networks {
156-
if net.IPAddress != "" {
157-
ollamaURL = fmt.Sprintf("http://%s:%s/api/tags", net.IPAddress, OllamaInternalPort)
158-
break
159-
}
160-
}
161-
}
141+
if info.State.Health != nil && info.State.Health.Status == "healthy" {
142+
slog.Info("ollama container is healthy", "id", containerID[:12])
143+
return nil
162144
}
163-
164-
if ollamaURL != "" {
165-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ollamaURL, nil)
166-
if err == nil {
167-
resp, err := httpClient.Do(req)
168-
if err == nil {
169-
io.Copy(io.Discard, resp.Body)
170-
resp.Body.Close()
171-
if resp.StatusCode == http.StatusOK {
172-
slog.Info("ollama container is healthy", "id", containerID[:12], "url", ollamaURL)
173-
return nil
174-
}
145+
slog.Debug("waiting for ollama to become healthy",
146+
"id", containerID[:12],
147+
"health", func() string {
148+
if info.State.Health != nil {
149+
return info.State.Health.Status
175150
}
176-
}
177-
}
178-
151+
return "no-healthcheck"
152+
}())
179153
select {
180154
case <-ctx.Done():
181155
return ctx.Err()

0 commit comments

Comments
 (0)