TryGhost/Ghost · error · Error

Egress monitor container has no network IP

Error message

Egress monitor container has no network IP

What it means

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.

Source

Thrown at e2e/helpers/environment/service-managers/egress-monitor.ts:178

                }
            },
            Labels: {
                // Same project label as Ghost/gateway so cleanupAllContainers() removes it.
                'com.docker.compose.project': TEST_ENVIRONMENT.projectNamespace,
                'tryghost/e2e': 'egress-monitor'
            }
        });
        await container.start();
        return container;
    }

    private async resolveIp(container: Container): Promise<string> {
        const info = await container.inspect();
        const networks = info.NetworkSettings?.Networks ?? {};
        const endpoint = networks[DEV_ENVIRONMENT.networkName] ?? Object.values(networks)[0];
        const ip = endpoint?.IPAddress;
        if (!ip) {
            throw new Error('Egress monitor container has no network IP');
        }
        return ip;
    }

    /** Parse every query CoreDNS has logged so far. */
    async readQueries(): Promise<EgressQuery[]> {
        if (!this.container) {
            return [];
        }
        const buffer = await this.container.logs({stdout: true, stderr: true, follow: false, timestamps: false});
        const queries: EgressQuery[] = [];
        for (const line of buffer.toString('utf8').split('\n')) {
            const match = line.match(EGRESS_LINE);
            if (match) {
                queries.push({
                    client: match[1],
                    type: match[2],
                    name: match[3].replace(/\.$/, '').toLowerCase()

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Retry resolveIp() with a short backoff (e.g. 3 attempts, 500ms) after start() — Docker often populates IPAddress a tick after start returns.
  2. Verify DEV_ENVIRONMENT.networkName matches the network the container was attached to; log info.NetworkSettings.Networks keys to confirm.
  3. Ensure the shared egress network is created before the monitor starts and not torn down concurrently by another worker.
  4. On Docker-in-Docker / rootless setups, confirm the bridge/overlay network allocates IPs (docker network inspect <name>).

Example fix

// before
await container.start();
return container;
...
const info = await container.inspect();

// after
await container.start();
let ip: string | undefined;
for (let i = 0; i < 5 && !ip; i++) {
    const info = await container.inspect();
    const nets = info.NetworkSettings?.Networks ?? {};
    const ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];
    ip = ep?.IPAddress;
    if (!ip) await new Promise(r => setTimeout(r, 500));
}
if (!ip) throw new Error('Egress monitor container has no network IP');
Defensive patterns

Strategy: retry

Validate before calling

// Before relying on the IP, confirm the container attached and got an address.
const info = await container.inspect();
const nets = info.NetworkSettings?.Networks ?? {};
const ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];
if (!ep?.IPAddress) {
    // wait and re-inspect before calling code that throws
}

Type guard

function hasNetworkIp(info: Docker.ContainerInspectInfo): boolean {
    const nets = info.NetworkSettings?.Networks ?? {};
    const ep = nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0];
    return Boolean(ep?.IPAddress);
}

Try / catch

let ip: string | undefined;
for (let attempt = 0; attempt < 5 && !ip; attempt++) {
    try {
        const info = await container.inspect();
        const nets = info.NetworkSettings?.Networks ?? {};
        ip = (nets[DEV_ENVIRONMENT.networkName] ?? Object.values(nets)[0])?.IPAddress;
    } catch { /* ignore transient inspect errors */ }
    if (!ip) await new Promise(r => setTimeout(r, 500));
}
if (!ip) throw new Error('Egress monitor container has no network IP');

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/dcd1d880193d5470. Report an issue: GitHub.