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
- 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>).
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
- 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.
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
- Egress monitor sidecar did not start in time
- Build image not found: ${BUILD_IMAGE}\n\nYou are running in
- Build gateway image not found: ${BUILD_GATEWAY_IMAGE}\n\nTo
- Ghost container became unhealthy during initialization
- Ghost container stopped during initialization
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/dcd1d880193d5470.
Report an issue: GitHub.