{"record":{"id":"dcd1d880193d5470","repo":"TryGhost/Ghost","slug":"egress-monitor-container-has-no-network-ip","errorCode":null,"errorMessage":"Egress monitor container has no network IP","messagePattern":"Egress monitor container has no network IP","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"e2e/helpers/environment/service-managers/egress-monitor.ts","lineNumber":178,"sourceCode":"                }\n            },\n            Labels: {\n                // Same project label as Ghost/gateway so cleanupAllContainers() removes it.\n                'com.docker.compose.project': TEST_ENVIRONMENT.projectNamespace,\n                'tryghost/e2e': 'egress-monitor'\n            }\n        });\n        await container.start();\n        return container;\n    }\n\n    private async resolveIp(container: Container): Promise<string> {\n        const info = await container.inspect();\n        const networks = info.NetworkSettings?.Networks ?? {};\n        const endpoint = networks[DEV_ENVIRONMENT.networkName] ?? Object.values(networks)[0];\n        const ip = endpoint?.IPAddress;\n        if (!ip) {\n            throw new Error('Egress monitor container has no network IP');\n        }\n        return ip;\n    }\n\n    /** Parse every query CoreDNS has logged so far. */\n    async readQueries(): Promise<EgressQuery[]> {\n        if (!this.container) {\n            return [];\n        }\n        const buffer = await this.container.logs({stdout: true, stderr: true, follow: false, timestamps: false});\n        const queries: EgressQuery[] = [];\n        for (const line of buffer.toString('utf8').split('\\n')) {\n            const match = line.match(EGRESS_LINE);\n            if (match) {\n                queries.push({\n                    client: match[1],\n                    type: match[2],\n                    name: match[3].replace(/\\.$/, '').toLowerCase()","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/helpers/environment/service-managers/egress-monitor.ts#L160-L196","documentation":"Thrown by EgressMonitor.resolveIp() after a container is started: the container was inspected but its NetworkSettings.Networks map yielded no endpoint with an IPAddress. The monitor (a CoreDNS-based egress sink) needs a routable IP on DEV_ENVIRONMENT.networkName (or the first available network) to forward queries to. Docker reports an empty IPAddress when the container is still pending network attachment, is on a dead network, or failed to join the named network.","triggerScenarios":"Calling resolveIp() immediately after container.start() returns, before Docker has assigned an IP. The container attached to a network whose name differs from DEV_ENVIRONMENT.networkName AND Object.values(networks)[0] is empty/undefined. Running on a Docker daemon with a broken/corrupted bridge network. Starting the monitor while another teardown is tearing down the shared network.","commonSituations":"CI runners where Docker networking initializes slowly; custom network name mismatch between constants.ts and the actual Docker network; rootless Docker or Docker-in-Docker where the first network is an internal-only one with no IP; a previous test leaked and the network was pruned mid-start.","solutions":["Retry resolveIp() with a short backoff (e.g. 3 attempts, 500ms) after start() — Docker often populates IPAddress a tick after start returns.","Verify DEV_ENVIRONMENT.networkName matches the network the container was attached to; log info.NetworkSettings.Networks keys to confirm.","Ensure the shared egress network is created before the monitor starts and not torn down concurrently by another worker.","On Docker-in-Docker / rootless setups, confirm the bridge/overlay network allocates IPs (docker network inspect <name>)."],"exampleFix":"// before\nawait container.start();\nreturn container;\n...\nconst info = await container.inspect();\n\n// after\nawait container.start();\nlet ip: string | undefined;\nfor (let i = 0; i < 5 && !ip; i++) {\n    const info = await container.inspect();\n    const nets = info.NetworkSettings?.Networks ?? {};\n    const ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];\n    ip = ep?.IPAddress;\n    if (!ip) await new Promise(r => setTimeout(r, 500));\n}\nif (!ip) throw new Error('Egress monitor container has no network IP');","handlingStrategy":"retry","validationCode":"// Before relying on the IP, confirm the container attached and got an address.\nconst info = await container.inspect();\nconst nets = info.NetworkSettings?.Networks ?? {};\nconst ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];\nif (!ep?.IPAddress) {\n    // wait and re-inspect before calling code that throws\n}","typeGuard":"function hasNetworkIp(info: Docker.ContainerInspectInfo): boolean {\n    const nets = info.NetworkSettings?.Networks ?? {};\n    const ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];\n    return Boolean(ep?.IPAddress);\n}","tryCatchPattern":"let ip: string | undefined;\nfor (let attempt = 0; attempt < 5 && !ip; attempt++) {\n    try {\n        const info = await container.inspect();\n        const nets = info.NetworkSettings?.Networks ?? {};\n        ip = (nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0])?.IPAddress;\n    } catch { /* ignore transient inspect errors */ }\n    if (!ip) await new Promise(r => setTimeout(r, 500));\n}\nif (!ip) throw new Error('Egress monitor container has no network IP');","preventionTips":["Always poll container.inspect for IPAddress after start() with a short backoff rather than assuming it's populated synchronously.","Ensure the shared egress network exists before any monitor starts and isn't torn down concurrently.","Log NetworkSettings.Networks keys on failure to catch network-name mismatches early."],"tags":["docker","network","e2e","egress-monitor","async-timing"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}