Skip to content

Commit f103f02

Browse files
committed
support attaching docker runners to multiple networks
1 parent 338a442 commit f103f02

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

apps/supervisor/src/env.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ const Env = z.object({
2929
RUNNER_SNAPSHOT_POLL_INTERVAL_SECONDS: z.coerce.number().optional(),
3030
RUNNER_ADDITIONAL_ENV_VARS: AdditionalEnvVars, // optional (csv)
3131
RUNNER_DOCKER_AUTOREMOVE: BoolEnv.default(true),
32+
/**
33+
* Network mode to use for all runners. Supported standard values are: `bridge`, `host`, `none`, and `container:<name|id>`.
34+
* Any other value is taken as a custom network's name to which all runners should connect to.
35+
*
36+
* Accepts a list of comma-separated values to attach to multiple networks. Additional networks are interpreted as network names and will be attached after container creation.
37+
*
38+
* **WARNING**: Specifying multiple networks will slightly increase startup times.
39+
*
40+
* @default "host"
41+
*/
42+
RUNNER_DOCKER_NETWORKS: z.string().default("host"),
3243

3344
// Dequeue settings (provider mode)
3445
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
@@ -43,7 +54,6 @@ const Env = z.object({
4354
TRIGGER_METADATA_URL: z.string().optional(),
4455

4556
// Used by the workload manager, e.g docker/k8s
46-
DOCKER_NETWORK: z.string().default("host"),
4757
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
4858
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
4959
KUBERNETES_IMAGE_PULL_SECRETS: z.string().optional(), // csv

apps/supervisor/src/workloadManager/docker.ts

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import {
77
import { env } from "../env.js";
88
import { getDockerHostDomain, getRunnerId } from "../util.js";
99
import Docker from "dockerode";
10+
import { tryCatch } from "@trigger.dev/core";
1011

1112
export class DockerWorkloadManager implements WorkloadManager {
1213
private readonly logger = new SimpleStructuredLogger("docker-workload-manager");
1314
private readonly docker: Docker;
1415

16+
private readonly runnerNetworks: string[];
17+
1518
constructor(private opts: WorkloadManagerOptions) {
1619
this.docker = new Docker();
1720

@@ -20,6 +23,8 @@ export class DockerWorkloadManager implements WorkloadManager {
2023
domain: opts.workloadApiDomain,
2124
});
2225
}
26+
27+
this.runnerNetworks = env.RUNNER_DOCKER_NETWORKS.split(",");
2328
}
2429

2530
async create(opts: WorkloadManagerCreateOptions) {
@@ -67,10 +72,16 @@ export class DockerWorkloadManager implements WorkloadManager {
6772
}
6873

6974
const hostConfig: Docker.HostConfig = {
70-
NetworkMode: env.DOCKER_NETWORK,
7175
AutoRemove: !!this.opts.dockerAutoremove,
7276
};
7377

78+
const [firstNetwork, ...remainingNetworks] = this.runnerNetworks;
79+
80+
// Always attach the first network at container creation time. This has the following benefits:
81+
// - If there is only a single network to attach, this will prevent having to make a separate request.
82+
// - If there are multiple networks to attach, this will ensure the runner won't also be connected to the bridge network
83+
hostConfig.NetworkMode = firstNetwork;
84+
7485
if (env.ENFORCE_MACHINE_PRESETS) {
7586
envVars.push(`TRIGGER_MACHINE_CPU=${opts.machine.cpu}`);
7687
envVars.push(`TRIGGER_MACHINE_MEMORY=${opts.machine.memory}`);
@@ -94,12 +105,71 @@ export class DockerWorkloadManager implements WorkloadManager {
94105
// Create container
95106
const container = await this.docker.createContainer(containerCreateOpts);
96107

108+
// If there are multiple networks to attach to we need to attach the remaining ones after creation
109+
if (remainingNetworks.length > 0) {
110+
await this.attachContainerToNetworks({
111+
containerId: container.id,
112+
networkNames: remainingNetworks,
113+
});
114+
}
115+
97116
// Start container
98117
const startResult = await container.start();
99118

100-
this.logger.debug("create succeeded", { opts, startResult, container, containerCreateOpts });
119+
this.logger.debug("create succeeded", {
120+
opts,
121+
startResult,
122+
containerId: container.id,
123+
containerCreateOpts,
124+
});
101125
} catch (error) {
102126
this.logger.error("create failed:", { opts, error, containerCreateOpts });
103127
}
104128
}
129+
130+
private async attachContainerToNetworks({
131+
containerId,
132+
networkNames,
133+
}: {
134+
containerId: string;
135+
networkNames: string[];
136+
}) {
137+
this.logger.debug("Attaching container to networks", { containerId, networkNames });
138+
139+
const [error, networkResults] = await tryCatch(
140+
this.docker.listNetworks({
141+
filters: {
142+
// Full name matches only to prevent unexpected results
143+
name: networkNames.map((name) => `^${name}$`),
144+
},
145+
})
146+
);
147+
148+
if (error) {
149+
this.logger.error("Failed to list networks", { networkNames });
150+
return;
151+
}
152+
153+
const results = await Promise.allSettled(
154+
networkResults.map((networkInfo) => {
155+
const network = this.docker.getNetwork(networkInfo.Id);
156+
return network.connect({ Container: containerId });
157+
})
158+
);
159+
160+
if (results.some((r) => r.status === "rejected")) {
161+
this.logger.error("Failed to attach container to some networks", {
162+
containerId,
163+
networkNames,
164+
results,
165+
});
166+
return;
167+
}
168+
169+
this.logger.debug("Attached container to networks", {
170+
containerId,
171+
networkNames,
172+
results,
173+
});
174+
}
105175
}

0 commit comments

Comments
 (0)