Skip to content

Commit 59c9162

Browse files
committed
fix: skip pollPorts retries for containers without published ports, skip redundant forwarders when host==container port
1 parent a43f7e6 commit 59c9162

3 files changed

Lines changed: 27 additions & 8 deletions

File tree

internal/daemon/daemon.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,25 +163,33 @@ func (d *Daemon) OnContainerStart(ctx context.Context, info watcher.ContainerInf
163163
continue
164164
}
165165

166+
newPortMappings = append(newPortMappings, state.PortMapping{
167+
ContainerPort: e.containerPort,
168+
HostPort: e.hostPort,
169+
Service: e.service,
170+
})
171+
172+
// When host port == container port, Docker's 0.0.0.0 binding already
173+
// covers the project loopback IP. No forwarder needed — DNS alone is enough.
174+
if e.hostPort == e.containerPort {
175+
slog.Info("direct access via Docker binding (host==container port)",
176+
"project", project, "service", e.service, "port", e.containerPort)
177+
continue
178+
}
179+
166180
targetAddr := fmt.Sprintf("127.0.0.1:%d", e.hostPort)
167181

168182
fwdCtx, fwdCancel := context.WithCancel(ctx)
169183
fwd := forwarder.New(listenAddr, targetAddr)
170184
go func() {
171185
if err := fwd.Start(fwdCtx); err != nil {
172-
slog.Error("forwarder error", "listen", listenAddr, "error", err)
186+
slog.Warn("forwarder bind failed", "listen", listenAddr, "error", err)
173187
}
174188
}()
175189

176190
d.forwarders[project] = append(d.forwarders[project], fwd)
177191
d.cancelFns[cancelKey] = fwdCancel
178192

179-
newPortMappings = append(newPortMappings, state.PortMapping{
180-
ContainerPort: e.containerPort,
181-
HostPort: e.hostPort,
182-
Service: e.service,
183-
})
184-
185193
slog.Info("forwarding", "project", project, "listen", listenAddr, "target", targetAddr)
186194
}
187195

internal/watcher/watcher.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,18 @@ func (w *Watcher) handleEvent(ctx context.Context, event events.Message) {
143143
}
144144

145145
func (w *Watcher) pollPorts(ctx context.Context, containerID string) (map[int]int, error) {
146-
for _, delay := range backoffSequence() {
146+
for i, delay := range backoffSequence() {
147147
inspect, err := w.docker.ContainerInspect(ctx, containerID)
148148
if err != nil {
149149
return nil, fmt.Errorf("inspect failed: %w", err)
150150
}
151151

152+
// On first inspect, check if the container even has port bindings configured.
153+
// Containers with only EXPOSE (no -p) will never have host port mappings.
154+
if i == 0 && (inspect.HostConfig == nil || len(inspect.HostConfig.PortBindings) == 0) {
155+
return nil, nil // no published ports, skip without error
156+
}
157+
152158
ports := extractPorts(inspect.NetworkSettings)
153159
if len(ports) > 0 {
154160
return ports, nil

internal/watcher/watcher_integration_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ func (h *mockEventHandler) getCalls() []handlerCall {
8686
// inspectWithPorts returns an InspectResponse with the given port mapping.
8787
func inspectWithPorts(ports nat.PortMap) container.InspectResponse {
8888
return container.InspectResponse{
89+
ContainerJSONBase: &container.ContainerJSONBase{
90+
HostConfig: &container.HostConfig{
91+
PortBindings: ports,
92+
},
93+
},
8994
NetworkSettings: &container.NetworkSettings{
9095
NetworkSettingsBase: container.NetworkSettingsBase{
9196
Ports: ports,

0 commit comments

Comments
 (0)