dockman
v0.4.1
Published
Build, tag, and push images with Docker or Podman from Bun and Node.
Readme
dockman
Build, tag, and push container images from Bun or Node with Docker or Podman, and deploy with kubectl.
Install with bun add dockman. Requires Node 22+ or Bun 1.3+ and an engine CLI on PATH.
import { DockmanConnection } from 'dockman'
const connection = await DockmanConnection.create({
preference: ['docker', 'podman'],
startIfNeeded: true,
interactive: true,
})
const image = await connection.build({
context: '.',
file: 'Dockerfile',
buildArgs: { NODE_ENV: 'production' },
})
await connection.tag(image, 'ghcr.io/my-org/my-app:latest')
await connection.push('ghcr.io/my-org/my-app:latest')Connecting
DockmanConnection.create(options?) checks engines in preference order, defaulting
to Docker then Podman. Use ['podman'] or ['docker'] to restrict discovery.
An already-running engine is used before attempting to start any engine. The readonly
connection.engine identifies the selected engine; operations never fail over to a
different image store. The CLI's existing environment and context/connection settings apply.
startIfNeeded defaults to true. Automatic startup is Windows-only: Dockman starts
Docker Desktop or Podman's existing default machine and polls for readiness.
startupTimeoutMs defaults to 120,000 per engine; individual probes have a five-second
limit. Set startIfNeeded: false to require an already-running engine. On macOS/Linux,
start engines yourself. Dockman does not install engines, initialize machines, elevate
privileges, change connection defaults, or stop engines after use.
Docker launches use hidden windows as a best effort. Docker controls its dashboard; if it still opens, disable Open Docker Dashboard when Docker Desktop starts in Docker's settings. Dockman does not change saved preferences.
Images
build(options?)streams progress and returns asha256:image ID. Options:context(default.),file,tags,buildArgs,labels,target,platform(one platform), andnoCache. An automatically cleaned temporary iidfile avoids parsing progress output. Build arguments are not a safe channel for secrets.tag(image, target)assigns one local name. Repeat it for additional tags.push(reference)streams progress and uploads an explicitly tagged reference. Any registry can use existing engine credentials; automatic login supports GHCR only.
Kubernetes deployments
KubernetesConnection uses kubectl on PATH and existing kubeconfig credentials independently
of Docker or Podman. Its constructor accepts context and namespace; omitted values use
kubectl's current settings. It does not change kubeconfig or contact the cluster until a method runs.
import { KubernetesConnection, kustomizeBuild, kustomizeSetImage } from 'dockman'
const cluster = new KubernetesConnection({ context: 'production', namespace: 'my-app' })
await kustomizeSetImage('./k8s', 'ghcr.io/example/my-app:v2')
const yaml = await kustomizeBuild('./k8s')
const diff = await cluster.diff(yaml)
console.log(diff)
await cluster.apply(yaml, {
dryRun: 'server', // Use 'none' to persist changes after your application's confirmation.
serverSide: true,
fieldManager: 'kustomize',
applySet: 'my-app-deploy',
})
// After applying with dryRun: 'none':
// await cluster.rolloutRestart('deploy/deployment')
// await cluster.rolloutStatus('deploy/deployment', { timeout: '10m' })diff(manifests, options?)returns the complete diff text and accepts kubectl exit codes 0 (unchanged) and 1 (differences). Other failures reject. Options includeserverSideandfieldManagerso previews can match apply semantics. kubectl requires a diff executable on PATH orKUBECTL_EXTERNAL_DIFFconfigured.get(resources, options?)returns parsed JSON for a namedtype/nameor a nonempty array oftype/nameselections. One selection returns a resource object; multiple selections return kubectl'sList. The result is typedunknown; validate its shape in your application. Missing names reject by default; a missing single selection produces an error identifying the requestedtype/name.ignoreNotFound: trueomits missing entries, and returnsundefinedfor empty output (including when all names are missing). Any JSON List, including an empty one, is returned unchanged. Authorization, connection, and other failures still reject. Output is captured in full and always treated as sensitive: neither stdout nor stderr is streamed, and errors omit subprocess diagnostics and JSON excerpts.getSecret(name, options?)retrieves one Secret by name (withoutsecret/) and returns its data asRecord<string, Buffer>, automatically decoding each base64 value and preserving binary data exactly. Usevalue.toString('utf8')for text. It retains the same context, namespace, and output protection asget(). Metadata is omitted; a Secret without data returns{}. Missing Secrets reject by default, or returnundefinedwithignoreNotFound: true. Invalid Secret data and malformed base64 reject with redacted errors.getConfigMap(name, options?)returnsRecord<string, string | Buffer>without metadata. It preservesdatastrings and combines them with base64-decodedbinaryDataBuffers. Empty ConfigMaps return{}; missing ConfigMaps reject unlessignoreNotFound: true, which returnsundefined. Malformed fields, invalid base64, and keys shared by both fields reject with redacted errors. Context, namespace, and output protection matchgetSecret(). Resource types have separate names:getSecret('kube-root-ca.crt')reports a missing Secret even if that name exists as a ConfigMap. UsegetConfigMap('kube-root-ca.crt')to retrieve it.apply(manifests, options?)streams output by default.dryRunaccepts'none'(default),'client', or'server'.serverSideandfieldManagercontrol field ownership. SettingapplySetenables--prune --applyset=..., removing previously managed resources absent from the manifests. It setsKUBECTL_APPLYSET=trueonly for that subprocess. Setsensitive: truefor Secrets: stdout and stderr are suppressed, the method returns no output, and failures contain a fixed message without subprocess diagnostics, original errors, or causes. This works with every dry-run mode, server-side apply, and ApplySet pruning.rolloutRestart(resource)restarts a resource specified astype/name.rolloutStatus(resource, { timeout? })streams progress until the latest rollout finishes, rejecting on failure or timeout. Durations use kubectl syntax, such as'10m'; the default waits indefinitely. A newer rollout is followed if one starts while watching.
Manifests accept strings, Uint8Array (including Node buffers), or ArrayBuffer and are
piped to stdin. Diff formatting and confirmation prompts remain in your script.
See the kubectl reference
for command behavior and prerequisites.
Secrets, ConfigMaps, and existence checks
Both resource types store flat key/value data. A Secret's data values represent arbitrary bytes
encoded as base64 in JSON; stringData is a write-only convenience merged into data by Kubernetes.
A ConfigMap has UTF-8 strings in data and optional base64 values in binaryData, with no overlapping
keys. A value may contain an entire JSON, YAML, or other file, but Kubernetes still treats it as one
string or byte sequence. These helpers return the data maps; use get() for resource
metadata or the original field structure. See the Secret API
and ConfigMap documentation.
const cluster = new KubernetesConnection({ context: 'production', namespace: 'my-app' })
// A single lookup makes an existence check explicit. Other failures still throw.
const namespaceExists =
(await cluster.get('namespace/my-app', { ignoreNotFound: true })) !== undefined
const deploymentExists =
(await cluster.get('deployment/web', { ignoreNotFound: true })) !== undefined
// Each name is explicit; this returns kubectl's JSON List without printing credentials.
const credentials = await cluster.get(['secret/app', 'secret/registry'])
// Validate credentials before persisting them to a protected local file. Do not log the result.
// For one Secret, get decoded Buffers directly without resource metadata.
const appCredentials = await cluster.getSecret('app')
const password = appCredentials.DB_PASSWORD?.toString('utf8')
// Buffers preserve every byte, including binary data and empty values. Do not log them.
// ConfigMaps preserve text as strings and decode binaryData into Buffers.
const appConfig = await cluster.getConfigMap('app')
const hostname = appConfig.DB_HOST
if (typeof hostname !== 'string') throw new Error('DB_HOST must be text')
// secretManifests is YAML or JSON read or constructed by your application.
await cluster.apply(secretManifests, {
sensitive: true,
serverSide: true,
fieldManager: 'my-app-deploy',
dryRun: 'server', // Use 'none' after review to persist changes.
applySet: 'my-app-secrets',
})All operations retain the connection's context and namespace, including resource retrieval.
The namespace flag does not change the scope of cluster-scoped resources such as Namespaces.
Selections are passed as separate arguments after --; resource names cannot inject kubectl flags.
See kubectl get for selection
and missing-resource semantics.
Sensitive mode protects Dockman's output and thrown errors; returned resources still contain the
original Secret data and must be handled privately by the caller. It does not alter manifests or
cluster-side logging. Current kubectl diff
masks Secret values in normal diff output by default (--show-secrets disables masking). Dockman's
diff() still includes raw command diagnostics on failure, so it does not provide the same output
protection as sensitive apply. kustomizeBuild() also returns manifest contents and includes CLI
diagnostics on failure; sensitive applies only to the apply() call, not to earlier rendering.
Use a separate ApplySet for Secrets if application deployments must not prune them.
Kustomize utilities
These standalone utilities require kustomize on PATH and work without a cluster or container
engine connection. Both require an explicit local directory containing the kustomization.
Relative paths resolve from the current working directory; neither utility changes it.
kustomizeBuild(directory)returns the complete UTF-8 YAML string, ready fordifforapply.kustomizeSetImage(directory, image)persists an image change to the local kustomization file. It accepts native image expressions such asghcr.io/example/my-app:v2,my-app=ghcr.io/example/my-app:v2, and digest references.
Both reject on command failures with CLI diagnostics. They use the standalone executable without falling back to kubectl's bundled Kustomize.
GHCR login
On recognizable GHCR authentication failures, interactive: true (the default)
allows GitHub CLI browser authentication in a terminal. Install gh beforehand.
Dockman reuses the active account, requests write:packages when needed, logs the
selected engine into ghcr.io using secret stdin, and retries the push once.
GitHub CLI and the engine manage credential persistence through their usual stores.
Generic permission denials, missing images, and network errors do not trigger login.
With interactive: false or no terminal, authentication failures include manual
instructions. For example:
gh auth login --hostname github.com --web --scopes write:packages
# For an existing account that needs the package scope:
gh auth refresh --hostname github.com --scopes write:packages
gh auth token --hostname github.com | docker login ghcr.io --username YOUR_GITHUB_USERNAME --password-stdinReplace docker with podman when appropriate. Verify package write permissions and
organization SSO authorization. Environment-provided GH_TOKEN/GITHUB_TOKEN values
take precedence in GitHub CLI; replace or unset insufficient tokens rather than
attempting browser refresh. Configure registry credentials ahead of time in CI.
See GitHub CLI login and GHCR authentication.
