Skip to content

Docker.Container reference

Source: src/Docker/Container.ts

A Docker container managed through the active Docker context.

This resource creates, starts, stops, inspects, and removes containers through the Docker CLI. It is not interchangeable with Cloudflare.Container, which manages Cloudflare’s container platform; use pushed image references to bridge Docker-built images into cloud container runtimes.

const nginx = yield* Docker.Container("nginx", {
image: "nginx:alpine",
ports: [{ external: 8080, internal: 80 }],
start: true,
});
const password = yield* Config.Redacted("POSTGRES_PASSWORD");
const db = yield* Docker.Container("postgres", {
image: "postgres:18-alpine",
environment: {
POSTGRES_PASSWORD: password,
},
start: true,
});
const app = yield* Docker.Container("app", {
image: "ghcr.io/acme/app:latest",
envFiles: ["./config/base.env", "./config/production.env"],
// Explicit values are passed after env files and take precedence.
environment: { LOG_LEVEL: "info" },
});

Editing an env file replaces the container on the next deploy, as does adopting a container that uses env files (once, so later edits are tracked). Alchemy reads the files at plan time but keeps only a digest of their contents, on the container’s own label; neither the values nor the digest are written to Alchemy state. Docker exposes the resolved values through docker inspect, so treat env files as secrets.

const network = yield* Docker.Network("app-network");
const data = yield* Docker.Volume("postgres-data");
const postgresName = "app-postgres";
yield* Docker.Container("postgres", {
name: postgresName,
image: "postgres:18-alpine",
ports: [{ external: 15432, internal: 5432 }],
volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }],
networks: [{ name: network.name, aliases: ["postgres"] }],
start: true,
});
const runtime = yield* Docker.inspectContainer(postgresName);

extraHosts writes lines into the container’s /etc/hosts; it changes name resolution and nothing else. Docker’s host-gateway alias resolves to the host machine, which is how a container reaches a service on the developer’s loopback.

On Linux host-gateway is the Docker bridge gateway (typically 172.17.0.1), so a container’s packets to it arrive on the host’s INPUT chain. Under a default-deny firewall — ufw ships DEFAULT_INPUT_POLICY="DROP" — the hostname resolves correctly and the connection then times out, which reads like an application bug rather than a firewall one. Allow the bridge subnet to fix it: sudo ufw allow from 172.16.0.0/12.

Reach a service on the developer’s machine

const api = yield* Docker.Container("api", {
image: "ghcr.io/acme/api:latest",
// `host-gateway` resolves to the host machine, so a database listening
// on the developer's loopback is reachable from inside the container.
extraHosts: ["host.docker.internal:host-gateway"],
environment: {
DATABASE_URL: "postgres://postgres@host.docker.internal:5432/app",
},
start: true,
});

Pin a hostname to a fixed address

const api = yield* Docker.Container("api", {
image: "ghcr.io/acme/api:latest",
// Any `hostname:address` pair — host access is just the common case.
extraHosts: ["service.example:192.0.2.10"],
start: true,
});

Share a donor container’s network namespace

const donor = yield* Docker.Container("donor", { image: "redis:alpine" });
const sidecar = yield* Docker.Container("sidecar", {
image: "busybox:latest",
networkMode: { container: donor.id },
});

Add capabilities and devices

const worker = yield* Docker.Container("worker", {
image: "ubuntu:latest",
capAdd: ["SYS_ADMIN"],
devices: [{ hostPath: "/dev/fuse", containerPath: "/dev/fuse" }],
});

Publish on any free host port

const api = yield* Docker.Container("api", {
image: "ghcr.io/acme/api:latest",
// `external: 0` lets Docker choose; the assigned port is reported back.
ports: [{ external: 0, internal: 3000 }],
start: true,
});
const hostPort = api.ports["3000/tcp"];

Route a container through Traefik

const api = yield* Docker.Container("api", {
image: "ghcr.io/acme/api:latest",
networks: [{ name: "traefik" }],
labels: {
"traefik.enable": "true",
"traefik.http.routers.api.rule": "Host(`api.example.com`)",
"traefik.http.services.api.loadbalancer.server.port": "3000",
},
stopTimeout: "30 seconds",
start: true,
});

Use a Docker.Context resource

const remote = yield* Docker.Context("remote", {
name: "remote-build",
docker: "host=ssh://docker@example.com",
});
const api = yield* Docker.Container("api", {
image: "nginx:alpine",
context: remote,
});