Skip to content

Tamp Docker

Scott Singleton edited this page May 10, 2026 · 4 revisions

Tamp.Docker

Wrapper for the Docker 27.x CLI. The image-publish workflow is the focus today: login, build, tag, push, pull, logout. Compose / volume / network / swarm aren't in scope for the first cut — they're plausible additions when there's demand.

using Tamp.Docker.V27;

Verbs

Docker.Login(s =>)        // docker login
Docker.Logout(s =>)       // docker logout
Docker.Build(s =>)        // docker build
Docker.Tag(s =>)          // docker tag
Docker.Push(s =>)         // docker push
Docker.Pull(s =>)         // docker pull

Each returns a CommandPlan. Common knobs (SetWorkingDirectory, EnvironmentVariables[k]=v) come from DockerSettingsBase.

Docker.Login

The headline feature: when a Secret password is supplied, the wrapper uses --password-stdin so the password value never appears on the command line or in the OS process table.

[Secret("Container registry password", EnvironmentVariable = "REGISTRY_PASSWORD")]
Secret RegistryPassword;

Target RegistryLogin => _ => _.Executes(() =>
    Docker.Login(s => s
        .SetServer("registry.example.com")
        .SetUsername("ci")
        .SetPassword(RegistryPassword)));    // Secret type

This emits:

docker login --username ci --password-stdin registry.example.com
stdin: <password value>

The password value is automatically registered with the executor's redaction table — any subsequent log line that happens to echo it gets scrubbed.

Settings: Server, Username, Password (Secret).

Docker.Logout

Docker.Logout(s => s.SetServer("registry.example.com"));
// or:  Docker.Logout()           // → docker logout (default registry)

Settings: Server.

Docker.Build

Docker.Build(s => s
    .SetContext(".")                                 // build context (positional last arg)
    .SetDockerfile("Dockerfile.prod")
    .AddTag("registry.example.com/myapp:latest")
    .AddTag($"registry.example.com/myapp:{Git.Commit[..7]}")
    .SetBuildArg("VERSION", "1.2.3")
    .SetLabel("org.opencontainers.image.source", "https://github.com/tamp-build/tamp")
    .SetPlatform("linux/amd64,linux/arm64")
    .SetNoCache(false)
    .SetPull(true));

Settings: Context (defaults to .), Dockerfile, Tags (list, repeated as multiple --tag), BuildArgs (dict, emitted as --build-arg KEY=VALUE), Labels (dict), Target, Platform, NoCache, Pull, Quiet, Network, OutputType, Progress.

Docker.Tag

Docker.Tag(s => s
    .SetSource("myapp:latest")
    .SetTarget("registry.example.com/myapp:1.2.3"));

Both SetSource and SetTarget are required; missing either throws at plan-construction time.

Docker.Push

Docker.Push(s => s
    .SetImage("registry.example.com/myapp:1.2.3"));

// or push every tag of an image:
Docker.Push(s => s
    .SetImage("registry.example.com/myapp")
    .SetAllTags(true));

Settings: Image (required), AllTags, Quiet, DisableContentTrust, Platform.

Docker.Pull

Docker.Pull(s => s
    .SetImage("alpine:3.20")
    .SetPlatform("linux/arm64"));

Settings: Image (required), AllTags, Quiet, DisableContentTrust, Platform.

Recipes

Build + tag + push (single-arch)

[GitRepository] readonly GitRepository Git;

string ImageBase => "registry.example.com/myapp";
string ImageTag => $"{ImageBase}:{Git.Commit[..7]}";
string ImageLatest => $"{ImageBase}:latest";

Target BuildImage => _ => _.Executes(() =>
    Docker.Build(s => s
        .SetContext(".")
        .AddTag(ImageTag)
        .AddTag(ImageLatest)
        .SetBuildArg("Version", "1.2.3")
        .SetPull(true)));

Target PushImage => _ => _
    .DependsOn(nameof(BuildImage), nameof(RegistryLogin))
    .Executes(() => new[]
    {
        Docker.Push(s => s.SetImage(ImageTag)),
        Docker.Push(s => s.SetImage(ImageLatest)),
    });

Multi-arch build (buildx setup is your responsibility)

Target BuildMultiArch => _ => _.Executes(() =>
    Docker.Build(s => s
        .SetContext(".")
        .AddTag(ImageTag)
        .SetPlatform("linux/amd64,linux/arm64")
        .SetOutputType("type=registry")));   // requires `docker buildx` and a builder

Cleanup with AssuredAfterFailure

Target Cleanup => _ => _
    .AssuredAfterFailure()
    .DependsOn(nameof(PushImage))
    .Executes(() => Docker.Logout(s => s.SetServer("registry.example.com")));

AssuredAfterFailure() ensures Logout runs even when an earlier target in the pipeline failed — important so credentials don't linger in the runner's Docker config.

Notes on V27

The package version pin is for the CLI surface that ships with Docker Desktop / Docker Engine 27.x. If your CI runner has an older Docker (24.x, 25.x, 26.x), most flags in this wrapper still work but a small subset are 27-only. Worth knowing:

  • --platform on pull requires Docker 24+
  • --all-tags on push requires Docker 23+
  • OutputType=type=registry (the buildx feature) requires buildx to be installed and a builder configured

For older runtimes that need a different CLI surface, a hypothetical Tamp.Docker.V24 could be published as a sibling package (per ADR 0002) — open an issue if you need this.

See also

Clone this wiki locally