docker
The docker spell forks the docker CLI (and hadolint) to build images and lint Dockerfiles. docker-build-check runs the builder's --check preflight without producing an image.
Runtime name: docker (source spells/docker/)
Version probe (docker): docker --version
Version probe (hadolint): hadolint --version
Version probe (trivy): trivy version
Passing arguments to ops
Every op is invoked as docker["<op>"](ctx, opts?). The first argument is the target's context, which is what carries the execution environment; the optional options map shapes the command itself:
| Key | Type | Description | Source |
|---|---|---|---|
args |
[str] |
Extra arguments appended to the resolved command, replacing any trailing defaults the op declares (go-test's ./...), so passing args also states the scope. Omit it and a bare docker["<op>"]() keeps the defaults and forwards magus run <target> -- <extra> to the tool automatically; pass it to set the arguments explicitly, which replaces that passthrough. To keep the passthrough too, append the target's own args parameter: {"args": ["-race", "./..."] + args}. |
source |
stdin |
str |
Data written to the command's standard input. | source |
Working directory and environment are NOT options: they ride the context, as docker["<op>"](ctx.withCwd("sub")) and docker["<op>"](ctx.withEnv({"CGO_ENABLED": "0"})). Only the context reaches the cache key, so an option-table cwd or env would change what the tool did while the key said otherwise; passing either as an option is an error.
Charms (the :charm suffix, e.g. magus run test:rw) are orthogonal: they patch the base argv, while these options add to it. See Charms.
docker-build
Command: docker build
Example
// docker-build's base command is just `docker build`, so pass the image tag and
// build context: `magus run image` forks `docker build -t app:latest .`.
import "magus";
import "magus/spell/docker";
magus\project({ "spells": [docker] });
export fun image(ctx: magus\Context, args: [str]) > void {
docker["docker-build"](ctx, { "args": ["-t", "app:latest", "."] });
}
docker-build-check
Command: docker build --check
Example
// docker-build-check runs the builder `--check` preflight over a context without
// producing an image; pass the build context (`.`).
import "magus";
import "magus/spell/docker";
magus\project({ "spells": [docker] });
export fun image_check(ctx: magus\Context, args: [str]) > void {
docker["docker-build-check"](ctx, { "args": ["."] });
}
docker-buildx
buildx is the op that PUSHES (--push), so it is the one that meets a registry and the one that fails on authentication. Docker's own message is a complete diagnosis to anyone who already knows docker and an exit code to everyone else, so the hints turn it into the command to run. Registries disagree about the wording - Docker Hub says "authentication required", GHCR and most OCI registries say "unauthorized" or "denied" - so each phrasing gets an entry rather than one guess. This is what a workspace should reach for before a separate <area>-login target: the failure teaches the fix, so nobody has to know a convention in advance. See docs/concepts/secrets.md.
Command: docker buildx build
Example
// docker-buildx builds with BuildKit; pass the tag and context. Add
// "--platform", "linux/amd64,linux/arm64" to the args for a multi-platform build.
import "magus";
import "magus/spell/docker";
magus\project({ "spells": [docker] });
export fun image_buildx(ctx: magus\Context, args: [str]) > void {
docker["docker-buildx"](ctx, { "args": ["-t", "app:latest", "."] });
}
docker-run
--rm is baked in: an op that leaves containers behind turns a repeated target into a disk leak. The caller supplies mounts, workdir, image and command through args.
Command: docker run --rm
hadolint
Lints the Dockerfile, reporting in the GNU diagnostic format the tool declares in mgs_getTools, so magus reads each finding's file, line and rule rather than its prose.
Command: hadolint -f gnu Dockerfile
Example
// hadolint lints the Dockerfile for common mistakes.
import "magus";
import "magus/spell/docker";
magus\project({ "spells": [docker] });
export fun lint(ctx: magus\Context, args: [str]) > void {
docker["hadolint"](ctx);
}
trivy-image
Scans an image OFFLINE by default: --skip-db-update is documented as "skip updating vulnerability database", so a plain run answers from the copy already on disk and downloads nothing (https://trivy.dev/latest/docs/references/configuration/cli/trivy_image/, read 2026-09-10). The caller appends the image reference and any output flags. The update charm drops the flag, which is the whole refresh: trivy pulls a stale database itself before scanning, so the arm needs no second command the way a separate --download-db-only pass would. Offline is the DEFAULT rather than the option because the charm is what says a run may move pinned upstream state forward, and a scan that silently refreshed its feed would be doing exactly that without being asked, and would change its verdict under an unchanged tree. What keeps that honest is the observe probe in mgs_getTools: the database on disk is in the cache key, so a scan replays only against the copy it was run against, and image-scan:update is what mints a new key. --offline-scan is NOT this flag and is deliberately not set: it stops trivy issuing API requests to identify dependencies (a Java-analysis concern), not database downloads.
Command: trivy image --skip-db-update
update
Drops --skip-db-update.
JSON Patch
[
{
"op": "remove",
"path": "/1"
}
]