@konfig.ts/docker
@konfig.ts/docker generates Dockerfiles for the apps in a Bun or Node monorepo. You write one docker.ts spec next to each app and the package works out the rest: it resolves the app’s transitive workspace dependencies and emits a Dockerfile that copies exactly those packages, so there is no hand-maintained COPY list to keep in sync. The output is a production multi-stage Dockerfile with base, deps, builder, and runner stages (plus a prod-deps stage when runner.production is set). If the spec has a dev block, it also emits a Dockerfile.dev made of the base and dev stages. The package stops at Dockerfiles: it does not build, push, tag, or sign images.
Install
bun add -d @konfig.ts/dockerThe base image tags come from the app’s package.json. It must set engines.<runtime> and engines.<package manager>, for example engines.bun, or engines.node plus engines.pnpm. A missing entry fails with EngineVersionMissing.
Usage
The API spec from the full-stack example. runner.production re-runs a production install after trimming workspaces to the closure.
export default Docker.app({
target: "apps/api",
runner: {
production: true,
workdir: "/app/apps/api",
copy: [Docker.copy.workspaceSourceAll()],
expose: 8080,
cmd: ["bun", "run", "src/main.ts"],
env: {
// per-env values and secrets come from Environment.bind, not here
LOG_LEVEL: "info"
},
healthcheck: {
tag: "HealthcheckHttpGet",
path: "/healthz",
port: 8080,
interval: "15s",
timeout: "3s",
retries: 3
}
},
dev: {
cmd: ["bun", "--watch", "src/main.ts"],
expose: 8080
}
})Render, write, or check the files with the CLI, then build with the monorepo root as the Docker context:
konfig docker preview apps/api # render to stdoutkonfig docker write apps/api # writes apps/api/Dockerfile and Dockerfile.dev; refuses to overwrite files without the konfig header unless --forcekonfig docker diff apps/api # non-zero exit if the on-disk files drifteddocker build -f apps/api/Dockerfile .Surface
| Export | Purpose |
|---|---|
Docker.app | The spec entrypoint; returns a DockerApp |
Docker.copy | builderArtifact({ src, dst, chown? }), workspaceSource(name), workspaceSourceAll(), path({ src, dst, from?, chown? }) |
Docker.runtime | bun({ alpine? }), node({ alpine? }); alpine defaults to true |
Docker.pm | bun(), npm(), pnpm(), yarn({ variant? }); auto-detected from the root lockfile when omitted |
Docker.build | script(name), command(argv), none(); when omitted, script("build") if the app has a build script, else none() |
Docker.healthcheck, Docker.user, Docker.platform | httpGet({ path, port, ... }) / command({ argv, ... }); nonRoot({ uid?, gid?, name? }) / root(); linuxAmd64() / linuxArm64() / multi([...]) |
DockerSpec, RunnerSpec, DevSpec, decodeDockerSpec, decodeDockerSpecSync | Schemas and decoders for the spec; the atom schemas (CopyAtom, RuntimeAtom, PackageManagerAtom, BuildAtom, HealthcheckAtom, UserAtom, PlatformAtom) are exported too |
findRoot, allWorkspaces, closureOf, detectPm | Workspace graph services |
buildIR, lower, prepareContext, validateSpec, emit, render, renderFile, renderHeader, extractHeader, HEADER_MARKER, sha256Hex | Building the intermediate representation and Dockerfile rendering with a hashed header |
bunPm, npmPm, pnpmPm, bunRuntime, nodeRuntime | Package manager and runtime implementations |
isDockerApp, makeDockerApp, DockerAppTypeId, PACKAGE_NAME | Spec identification helpers |
Errors
Every failure the package can produce is a tagged error, and AnyDockerError is the union of all of them.
| Error | When it is raised |
|---|---|
MonorepoRootNotFound | No monorepo root could be found walking up from the starting directory |
WorkspaceNotFound | The target app is not a workspace of the monorepo |
UnsupportedPm | The package manager could not be determined: no packageManager field and either no recognized lockfile or several conflicting ones |
CircularWorkspaceDep | The workspace graph contains a cycle |
EngineVersionMissing | The app’s package.json lacks the required engines.* entry |
SpecDecodeError | The docker.ts spec did not decode against the schema |
BuildScriptMissing | The spec names a build script the app’s package.json does not define |
WorkspaceSourceUnknown | A workspaceSource(name) copy names a workspace outside the app’s closure |
SharedRootFileMissing | A file listed in sharedRootFiles does not exist at the monorepo root |
PlatformMultiUnsupported | Docker.platform.multi([...]) was used; a single FROM --platform line cannot express multiple platforms |
DockerWriteRefused | write would overwrite a file that lacks the konfig header and --force was not given |
DockerWriteError | Writing a Dockerfile to disk failed |
Requirements
effect@^4.0.0-rc.111as a peer dependency (Effect 4, release-candidate line).- Depends on
@konfig.ts/coreandyaml. - Runtime: Bun recommended; Node >= 23.6 works; Node 22.6 to 23.5 with
--experimental-strip-types;tsxworks.
Source and README: packages/docker.