Skip to content

Docker.Swarm reference

Source: src/Docker/Service.ts

A Docker Swarm service: N replicas of a container kept alive by the swarm, deployed through the active (or a named) Docker context.

The target engine must be a swarm manager — Service wraps docker service, swarm mode’s orchestration API. Declare the swarm with Docker.Swarm and pass it as context so the service deploys after the swarm exists; for a plain single container on a non-swarm daemon use Docker.Container instead.

The service’s image comes from one of two sources:

  • image — run a pre-built reference (a registry ref, or a Docker.Image / Docker.RemoteImage resource).
  • main — bundle an inline Effect program into a generated bun image, built directly against the service’s Docker context. The impl returns { fetch } and may register background loops via ServerHost.run — the same effectful platform shape as AWS.ECS.Service.

The bundled image is content-addressed and only rebuilt when the program (or its generated Dockerfile) changes. It is built on the target engine’s local store — single-node swarms run it as-is; multi-node swarms need the image on a registry every node can reach (build with Docker.Image + registry and pass the pushed ref as image instead).

Only replicated services are supported. Configuration changes replace the service (delete-then-create); swarm tasks are stateless, so replacement is cheap and avoids partially-applied service update drift.

Replicated Nginx

const swarm = yield* Docker.Swarm("swarm");
const web = yield* Docker.Service("web", {
context: swarm,
image: "nginx:alpine",
replicas: 3,
ports: [{ external: 8080, internal: 80 }],
});

Run a Built Image

const image = yield* Docker.Image("app-image", {
build: { context: "./app" },
});
const app = yield* Docker.Service("app", {
context: swarm,
image,
replicas: 2,
});

Inline Effect Server

const swarm = yield* Docker.Swarm("swarm");
const api = yield* Docker.Service(
"Api",
{
context: swarm,
main: import.meta.url,
port: 3000,
ports: [{ external: 8080, internal: 3000 }],
replicas: 2,
},
Effect.gen(function* () {
return {
fetch: Effect.gen(function* () {
return yield* HttpServerResponse.json({ ok: true });
}),
};
}),
);

Background Loops with ServerHost

// Class props may be an Effect, so the service can yield the swarm it
// deploys into (declared once at module level).
const Swarm = Docker.Swarm("swarm");
export default class Worker extends Docker.Service<Worker>()(
"Worker",
Effect.gen(function* () {
const swarm = yield* Swarm;
return { context: swarm, main: import.meta.url, port: 3000 };
}),
Effect.gen(function* () {
const host = yield* ServerHost;
yield* host.run(
pollQueue.pipe(Effect.repeat(Schedule.spaced("5 seconds")), Effect.asVoid),
);
return {
fetch: Effect.succeed(HttpServerResponse.text("ok")),
};
}),
) {}

main is bundled with rolldown at deploy time. Unused code is tree-shaken. effect, alchemy, and @distilled.cloud are marked pure so unused parts prune more aggressively. Your app is not marked pure.

Mark additional packages as pure

Only list packages with no top-level side effects.

{
main: import.meta.url,
build: {
pure: { packages: ["my-lib", "@my-scope/*"] },
},
}

Turn it off

{
main: import.meta.url,
build: { pure: false },
}
const vps = yield* Docker.Context("vps", {
docker: "host=ssh://deploy@example.com",
});
const swarm = yield* Docker.Swarm("swarm", {
context: vps,
advertiseAddr: "10.0.0.1",
});
const app = yield* Docker.Service("app", {
context: swarm,
image: "nginx:alpine",
replicas: 3,
});
const network = yield* Docker.Network("app-net", {
context: swarm,
driver: "overlay",
});
const db = yield* Docker.Service("db", {
context: swarm,
image: "postgres:18-alpine",
networks: [{ name: network.name, aliases: ["postgres"] }],
volumes: [{ hostPath: "pg-data", containerPath: "/var/lib/postgresql/data" }],
});
const app = yield* Docker.Service("app", {
image: "ghcr.io/acme/app:latest",
replicas: 4,
updateConfig: {
parallelism: 1,
delay: "10s",
failureAction: "rollback",
order: "start-first",
},
placement: {
constraints: ["node.role==worker"],
maxReplicasPerNode: 2,
},
});
const app = yield* Docker.Service("app", {
image: "ghcr.io/acme/app:latest",
secrets: [{ source: "db-password", target: "db_password", mode: 0o400 }],
configs: [{ source: "app-config", target: "/etc/app/config.yaml" }],
});

Source: src/Docker/Swarm.ts

Swarm mode on a Docker engine — an idempotent docker swarm init.

Turns the engine behind the given context (or the local engine) into a single-node swarm: the node becomes a manager that can run Docker.Service workloads. Regular (non-swarm) docker usage of the engine is unaffected.

Pass the swarm as the context of a Docker.Service or overlay Docker.Network: the workload then deploys after the swarm exists and inherits its Docker context.

An engine that is already in swarm mode is treated as foreign — adopt it with adopt(true) (or --adopt) to manage it. Destroying the resource dissolves the node’s swarm membership (docker swarm leave --force), which stops every service running on it — services managed by the same stack are destroyed first via their dependency edges.

Growing the cluster beyond one node is host-level setup: run docker swarm join --token <token> <manager-ip>:2377 on each additional machine (docker swarm join-token worker on the manager prints the command). Alchemy manages the swarm’s workloads through the manager.

Local single-node swarm

const swarm = yield* Docker.Swarm("swarm");

Remote engine over SSH

const vps = yield* Docker.Context("vps", {
docker: "host=ssh://deploy@example.com",
});
const swarm = yield* Docker.Swarm("swarm", {
context: vps,
advertiseAddr: "10.0.0.1",
});

Reference without owning

// Services don't require a Swarm resource — point them at an engine that
// is already a manager and the swarm's lifecycle stays external: destroy
// removes the services, never the swarm.
const web = yield* Docker.Service("web", {
context: vps,
image: "nginx:alpine",
});

Adopt an already-initialized engine

// Adoption makes the swarm part of the stack — destroy then dissolves
// the node's membership.
const swarm = yield* Docker.Swarm("swarm").pipe(adopt(true));
const swarm = yield* Docker.Swarm("swarm");
const web = yield* Docker.Service("web", {
context: swarm,
image: "nginx:alpine",
replicas: 2,
ports: [{ external: 8080, internal: 80 }],
});