From d333860ced311b8b05b35e3632cc6adbc55a9b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erhan=20=C3=9CRG=C3=9CN?= Date: Thu, 23 Jul 2026 11:47:36 +0300 Subject: [PATCH] fix(podman): mount managed /etc/hosts for all custom services Custom services without ShareHosts received no /etc/hosts mount, so podman synthesised the file from base_hosts_file, which defaults to the host's own /etc/hosts. Any "127.0.0.1 lerd-" line there (a stale entry from an older lerd, or one written for a client shim / host-proxy app) then shadows the container-DNS name: sidecar services such as phpmyadmin, pgadmin and mongo-express resolve lerd-mysql to their own loopback and fail with "mysqli::real_connect(): (HY000/2002): Connection refused". Mount the managed container hosts file in the else branch so every custom service overrides host inheritance. ShareHosts services keep the browser-hosts variant for .test resolution. PHP-FPM containers already bind-mount this file; this brings sidecar services in line. --- internal/podman/quadlet_embed_test.go | 15 +++++++++++++-- internal/podman/quadlet_generate.go | 12 ++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/podman/quadlet_embed_test.go b/internal/podman/quadlet_embed_test.go index a792e0ebf..504892b61 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 5e1337dc4..838e10ada 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 {