Add support for private registries #1

Open
opened 2026-07-28 12:03:33 -06:00 by w33ble · 0 comments
Owner

The scope: 3 files, ~40 lines of code

The only place auth is needed is internal/registry/inspect.go — remote.Get calls the registry directly. Pulls go through the Docker daemon, which handles its own auth (ImagePull uses daemon-side ~/.docker/config.json).

  1. Config: add a RegistryAuth field

internal/config/config.go:

type Config struct {
    // ... existing fields ...
    RegistryAuth map[string]RegistryCredential `json:"registry_auth,omitempty"`
}

type RegistryCredential struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

Or, simpler and more secure for env vars — one flat LAGOM_REGISTRY_USERNAME / LAGOM_REGISTRY_PASSWORD pair (covers the single-registry case most users have). A single env var like LAGOM_REGISTRY_AUTH=registry.example.com:user:pass is also an option, but multi-registry gets ugly fast.

Recommendation: Support ~/.docker/config.json via authn.DefaultKeychain automatically (zero config for users who docker login), plus an explicit LAGOM_REGISTRY_AUTH JSON blob for headless/container use:
LAGOM_REGISTRY_AUTH='{"ghcr.io":{"username":"me","password":"token"}}'

  1. Wire auth into InspectTag

internal/registry/inspect.go:

import "github.com/google/go-containerregistry/pkg/authn"

func InspectTag(ctx context.Context, ref string, auth map[string]RegistryCredential) (*RemoteInfo, error) {
    // ... parse tag ...

    keychain := authn.DefaultKeychain  // reads ~/.docker/config.json
    desc, err := remote.Get(tag,
        remote.WithContext(ctx),
        remote.WithAuthFromKeychain(keychain),
    )
    // ... rest unchanged ...
}

For explicit credentials, create an authn.Basic authenticator for the registry and use remote.WithAuth:

if cred, ok := auth[tag.RegistryStr()]; ok {
    authOpt := remote.WithAuth(&authn.Basic{Username: cred.Username, Password: cred.Password})
    desc, err = remote.Get(tag, remote.WithContext(ctx), authOpt)
}

Priority: explicit config > DefaultKeychain (Docker config.json) > anonymous.

  1. Plumb auth through callers

Three call sites — all in internal/cmd/:

File Call Change
actions.go:90 registry.InspectTag(ctx, ref) — TrackImages pass cfg.RegistryAuth
actions.go:288 registry.InspectTag(ctx, ref) — UpdatePlan pass cfg.RegistryAuth
status.go registry.InspectTag(ctx, ref) — status command pass cfg.RegistryAuth

If InspectTag reads cfg directly (package-level init), no plumbing needed — just read it in the function. That's simpler:
var authConfig map[string]RegistryCredential // set at startup from cfg

Or pass it as a parameter — cleaner but more diff.

  1. Non-code: docs + Docker setup
  • README: new config field, env var, Docker run example with -v ~/.docker/config.json:/root/.docker/config.json:ro
  • Existing Docker image already runs as root — DefaultKeychain looks at /root/.docker/config.json automatically
    What this doesn't cover (v2):
  • Credential helpers (docker-credential-ecr-login, etc.) — DefaultKeychain handles these if the helper binary is in the container, but that's an edge case
  • Multi-registry with per-registry credentials in config file
  • OAuth / identity-token-based auth (ECR, GCR workload identity)
**The scope: 3 files, ~40 lines of code** The only place auth is needed is internal/registry/inspect.go — remote.Get calls the registry directly. Pulls go through the Docker daemon, which handles its own auth (ImagePull uses daemon-side ~/.docker/config.json). 1. Config: add a RegistryAuth field internal/config/config.go: ```go type Config struct { // ... existing fields ... RegistryAuth map[string]RegistryCredential `json:"registry_auth,omitempty"` } type RegistryCredential struct { Username string `json:"username"` Password string `json:"password"` } ``` Or, simpler and more secure for env vars — one flat LAGOM_REGISTRY_USERNAME / LAGOM_REGISTRY_PASSWORD pair (covers the single-registry case most users have). A single env var like LAGOM_REGISTRY_AUTH=registry.example.com:user:pass is also an option, but multi-registry gets ugly fast. Recommendation: Support ~/.docker/config.json via authn.DefaultKeychain automatically (zero config for users who docker login), plus an explicit LAGOM_REGISTRY_AUTH JSON blob for headless/container use: LAGOM_REGISTRY_AUTH='{"ghcr.io":{"username":"me","password":"token"}}' 2. Wire auth into InspectTag internal/registry/inspect.go: ```go import "github.com/google/go-containerregistry/pkg/authn" func InspectTag(ctx context.Context, ref string, auth map[string]RegistryCredential) (*RemoteInfo, error) { // ... parse tag ... keychain := authn.DefaultKeychain // reads ~/.docker/config.json desc, err := remote.Get(tag, remote.WithContext(ctx), remote.WithAuthFromKeychain(keychain), ) // ... rest unchanged ... } ``` For explicit credentials, create an authn.Basic authenticator for the registry and use remote.WithAuth: ```go if cred, ok := auth[tag.RegistryStr()]; ok { authOpt := remote.WithAuth(&authn.Basic{Username: cred.Username, Password: cred.Password}) desc, err = remote.Get(tag, remote.WithContext(ctx), authOpt) } ``` Priority: explicit config > DefaultKeychain (Docker config.json) > anonymous. 3. Plumb auth through callers Three call sites — all in internal/cmd/: File | Call | Change --- | --- | --- actions.go:90 | registry.InspectTag(ctx, ref) — TrackImages | pass cfg.RegistryAuth actions.go:288 | registry.InspectTag(ctx, ref) — UpdatePlan | pass cfg.RegistryAuth status.go | registry.InspectTag(ctx, ref) — status command | pass cfg.RegistryAuth If InspectTag reads cfg directly (package-level init), no plumbing needed — just read it in the function. That's simpler: var authConfig map[string]RegistryCredential // set at startup from cfg Or pass it as a parameter — cleaner but more diff. 4. Non-code: docs + Docker setup - README: new config field, env var, Docker run example with -v ~/.docker/config.json:/root/.docker/config.json:ro - Existing Docker image already runs as root — DefaultKeychain looks at /root/.docker/config.json automatically What this doesn't cover (v2): - Credential helpers (docker-credential-ecr-login, etc.) — DefaultKeychain handles these if the helper binary is in the container, but that's an edge case - Multi-registry with per-registry credentials in config file - OAuth / identity-token-based auth (ECR, GCR workload identity)
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
w33ble/lagom#1
No description provided.