diff --git a/internal/podman/quadlet_embed_test.go b/internal/podman/quadlet_embed_test.go index a792e0eb..504892b6 100644 --- a/internal/podman/quadlet_embed_test.go +++ b/internal/podman/quadlet_embed_test.go @@ -404,8 +404,19 @@ func TestGenerateCustomQuadlet_NoShareHosts(t *testing.T) { Image: "docker.io/library/mongo:7", } out := GenerateCustomQuadlet(svc) - if strings.Contains(out, "/etc/hosts") { - t.Errorf("should not mount hosts file when ShareHosts=false, got:\n%s", out) + // Even without ShareHosts, a sidecar service must mount lerd's managed + // /etc/hosts. Otherwise podman falls back to base_hosts_file (the host's + // /etc/hosts by default), where a stale or client-shim "127.0.0.1 lerd-" + // entry shadows the container-DNS name and breaks connections — e.g. + // lerd-phpmyadmin resolving lerd-mysql to its own loopback (#issue). + wantVolume := "Volume=" + config.ContainerHostsFile() + ":/etc/hosts:ro,z" + if !strings.Contains(out, wantVolume) { + t.Errorf("ShareHosts=false must mount the managed container hosts file to override host inheritance, got:\n%s", out) + } + // The browser-testing hosts variant is reserved for ShareHosts=true. + browserVolume := "Volume=" + config.BrowserHostsFile() + ":/etc/hosts:ro,z" + if strings.Contains(out, browserVolume) { + t.Errorf("ShareHosts=false must not mount the browser hosts file, got:\n%s", out) } } diff --git a/internal/podman/quadlet_generate.go b/internal/podman/quadlet_generate.go index 5e1337dc..838e10ad 100644 --- a/internal/podman/quadlet_generate.go +++ b/internal/podman/quadlet_generate.go @@ -50,8 +50,20 @@ func GenerateCustomQuadlet(svc *config.CustomService) string { b.WriteString("PodmanArgs=--init\n") } + // Always mount a lerd-managed /etc/hosts. Without an explicit mount podman + // synthesises the file from base_hosts_file, which defaults to the host's + // own /etc/hosts — so any "127.0.0.1 lerd-" entry there (a stale line + // from an older lerd, or one written for a client shim / host-proxy app) + // shadows the container-DNS name and the service resolves to its own + // loopback instead of the peer container, e.g. lerd-phpmyadmin failing to + // reach lerd-mysql with "Connection refused". PHP-FPM containers already + // bind-mount this file; sidecar services (phpmyadmin, pgadmin, mongo-express) + // need it too. ShareHosts services additionally want .test domains resolved + // to nginx, so they get the browser-hosts variant. if svc.ShareHosts { fmt.Fprintf(&b, "Volume=%s:/etc/hosts:ro,z\n", config.BrowserHostsFile()) + } else { + fmt.Fprintf(&b, "Volume=%s:/etc/hosts:ro,z\n", config.ContainerHostsFile()) } for _, port := range svc.Ports {