Skip to main content

Docker Networking

Port publishing and network membership are both docker run-time concerns, so they live on the ephemeral container builder, not on the plain $.docker({...}) exec/run shorthand. Network administration itself (create/connect/disconnect/inspect) is its own handle off $.docker().

import { $ } from '@xec-sh/core';

await $.docker().network('app-network').create({ driver: 'bridge' });

await $.docker()
.ephemeral('nginx:alpine')
.name('web')
.network('app-network')
.ports(['80:80'])
.start();

Ports​

Only the ephemeral builder publishes ports:

const web = $.docker().ephemeral('nginx:alpine').name('web');

web.ports(['80:80']); // host:container, raw -p syntax
web.ports(['127.0.0.1:8080:8080']); // bind to one interface
web.ports(['9229:9229/udp']); // protocol suffix
web.port(3000, 3000); // single mapping, same effect as ports(['3000:3000'])
web.port(5000, 5000, 'udp');

.ports() appends raw strings straight to -p, so anything the docker run -p flag accepts works, including port ranges ('8000-8010:8000-8010'). .port(host, container, protocol?) is a typed convenience for the common single-mapping case.

The plain object shorthand has no equivalent — $.docker({ image: 'nginx', ports: [...] }) accepts ports at the type level (DockerEphemeralOptions) but the engine method never forwards it to docker run. Use the ephemeral builder for anything beyond image/volumes/workdir/user.

Creating and Managing Networks​

const network = $.docker().network('app-network');

await network.create({
driver: 'bridge', // 'bridge' | 'host' | 'overlay' | 'macvlan' | 'none'
subnet: '172.20.0.0/16',
gateway: '172.20.0.1',
ipRange: '172.20.1.0/24',
attachable: true,
internal: false,
labels: { app: 'myapp' },
});

await network.connect('my-app', { alias: ['app', 'web'] }); // note: `alias`, singular field, array value
await network.connect('my-app', { ip: '172.20.0.5' });
await network.disconnect('my-app');
await network.disconnect('my-app', true); // force

console.log(await network.inspect()); // raw `docker network inspect` JSON
console.log(await network.exists());
await network.remove();

The lower-level adapter has the same create/remove/list operations, minus labels:

const docker = $.getAdapter('docker') as import('@xec-sh/core').DockerAdapter;

await docker.createNetwork('backend', { driver: 'bridge', internal: true });
const names = await docker.listNetworks();
await docker.removeNetwork('backend');

createNetwork is idempotent — it checks listNetworks() first and returns quietly if the name already exists, and also swallows the daemon's own "already exists" error if one slips through a race.

Connecting an Existing Container to Another Network​

An ephemeral or persistent container only joins the network(s) given at docker run time (or none, beyond the default bridge, for docker exec on something already running). To add a running container to another network afterward, use network.connect():

await $.docker().network('app-network').create();
await $.docker().network('app-network').connect('db', { alias: ['database'] });
await $.docker().network('app-network').connect('app');

// Containers sharing a network resolve each other by name or alias
await $.docker('app')`ping -c 1 database`;

Inspecting a Container's Network State​

.info() on either lifecycle builder returns ip (the default network's address) and networks (names of every network the container is on):

const info = await $.docker().container('app').info();
console.log(info?.ip, info?.networks);