Docker Credential Helper Tutorial#

This tutorial shows how to use scafctl as a Docker/Podman credential helper, exposing scafctl’s AES-256-GCM encrypted credential store to the container ecosystem.

Prerequisites#

  • scafctl installed and available in your PATH
  • Docker, Podman, or Buildah installed
  • Familiarity with Authentication Tutorial

Table of Contents#

  1. Overview
  2. Quick Setup
  3. Manual Setup
  4. Per-Registry Configuration
  5. How It Works
  6. Using with Podman/Buildah
  7. Verification
  8. Composing with catalog login
  9. Uninstall
  10. Troubleshooting

Overview#

The Docker credential helper protocol allows external programs to manage registry credentials for Docker, Podman, and Buildah. By using scafctl as a credential helper, you get:

  • Encrypted-at-rest credentials – scafctl stores all secrets using AES-256-GCM encryption
  • Single credential store – credentials stored via scafctl catalog login are automatically available to Docker
  • No plaintext files – unlike Docker’s default config.json auth storage

Quick Setup#

Install scafctl as the credential helper for Docker:

scafctl credential-helper install --docker

This creates a docker-credential-scafctl symlink in ~/.local/bin and updates ~/.docker/config.json to use scafctl as the credential store.

For Podman:

scafctl credential-helper install --podman

Manual Setup#

scafctl credential-helper install

This creates ~/.local/bin/docker-credential-scafctl pointing to your scafctl binary. Ensure ~/.local/bin is on your PATH:

export PATH="$HOME/.local/bin:$PATH"

2. Configure Docker#

Add to ~/.docker/config.json:

{
  "credsStore": "scafctl"
}

This tells Docker to use scafctl for all registry credential lookups.

Per-Registry Configuration#

Instead of making scafctl the global credential store, you can configure it for specific registries only:

scafctl credential-helper install --docker --registry ghcr.io

This adds a credHelpers entry to Docker’s config:

{
  "credHelpers": {
    "ghcr.io": "scafctl"
  }
}

You can add multiple registries:

scafctl credential-helper install --docker --registry ghcr.io
scafctl credential-helper install --docker --registry quay.io

How It Works#

The credential helper protocol uses four stdin/stdout commands:

CommandInputOutputDescription
getServer URL (text)JSON credentialRetrieve credentials
storeJSON credentialStore credentials
eraseServer URL (text)Remove credentials
listJSON mapList all credentials

Credential Format#

{
  "ServerURL": "https://ghcr.io",
  "Username": "oauth2",
  "Secret": "gho_xxxxxxxxxxxx"
}

Error Format#

{
  "message": "credentials not found"
}

Lookup Order#

When Docker calls get, scafctl checks credentials in this order:

  1. Dynamic token resolution – infers the auth handler for the registry and mints a fresh token from it (auto-refreshing via a stored refresh token when available)
  2. credhelper namespace – credentials stored via docker login (through scafctl)
  3. Native credential store – credentials stored via scafctl catalog login

This means credentials from any source are available to Docker.

Note: get runs as a non-interactive subprocess (no TTY). If the inferred auth handler has no valid token and can only refresh through an interactive login, dynamic resolution cannot complete inside the helper. scafctl emits an actionable error naming the exact command to run, for example:

{
  "message": "no valid credentials for https://ghcr.io: run 'scafctl auth login github' to re-authenticate (interactive login cannot run inside a credential-helper subprocess)"
}

Run the named auth login once; subsequent get calls then refresh silently.

Using with Podman/Buildah#

Podman and Buildah use the same credential helper protocol:

scafctl credential-helper install --podman

Or manually configure ~/.config/containers/auth.json:

{
  "credHelpers": {
    "ghcr.io": "scafctl"
  }
}

Verification#

Test the credential helper directly:

# List all stored credentials
docker-credential-scafctl list

# Get credentials for a specific registry
echo "https://ghcr.io" | docker-credential-scafctl get

# Verify Docker can use it
docker pull ghcr.io/your-org/your-image:latest

Composing with catalog login#

Credentials stored via scafctl catalog login are automatically available through the credential helper:

# Login via scafctl
scafctl catalog login ghcr.io --username oauth2 --password @- < token.txt

# Docker can now pull from ghcr.io without separate docker login
docker pull ghcr.io/your-org/your-image:latest

Embedding scafctl in another CLI#

scafctl is consumable as a library by downstream CLIs that ship their own binary name. The credential helper works for embedders with no extra wiring: when the binary is invoked under a docker-credential-<name> alias, the shared root command automatically routes the helper verb into the credential-helper command tree.

If you build your CLI via the shared scafctl.Root(...) constructor, you get this for free – credential-helper install creates a docker-credential-<yourbinary> symlink, and Docker/Podman invoking it just works.

For a fully custom main that does not use scafctl.Root, call the exported, side-effect-free dispatch helper at the very top of main:

import "github.com/oakwood-commons/scafctl/pkg/credentialhelper"

func main() {
    // Route docker-credential-<name> <verb> into "credential-helper <verb>".
    if rewritten, isAlias := credentialhelper.RewriteAliasArgs(os.Args); isAlias {
        rootCmd.SetArgs(rewritten[1:]) // SetArgs excludes the program name
    }
    // ... normal command execution ...
}

The actionable get error (see How It Works ) is derived from your configured binary name, so it names your-binary auth login <handler>.

Uninstall#

# Remove symlink and Docker config entry
scafctl credential-helper uninstall --docker

# Or just the symlink
scafctl credential-helper uninstall

# For Podman
scafctl credential-helper uninstall --podman

Troubleshooting#

“docker-credential-scafctl: command not found”#

Ensure ~/.local/bin (or your custom --bin-dir) is on your PATH:

export PATH="$HOME/.local/bin:$PATH"

“credentials not found”#

  1. Verify the credential exists: docker-credential-scafctl list
  2. Check if you logged in with scafctl: scafctl catalog login <registry>
  3. Check the server URL matches exactly (including scheme)

Master key issues#

The credential helper uses scafctl’s encrypted secrets store. If the master key is not available, all operations will fail. Ensure:

# Check if the secrets store is accessible
scafctl secrets list

If you can’t write to ~/.local/bin, specify a different directory:

scafctl credential-helper install --bin-dir /usr/local/bin --docker