wagoodman/dive · critical

failed to unmarshal docker config: %w

Error message

failed to unmarshal docker config: %w

What it means

Raised as a panic by newConfig (dive/image/docker/config.go:31) when json.Unmarshal of the image's config JSON fails. The config blob is the <id>.json file from a docker archive (or the OCI config); any structural mismatch between the blob and the config struct aborts analysis with 'failed to unmarshal docker config: %w'.

Source

Thrown at dive/image/docker/config.go:31

type rootFs struct {
	Type    string   `json:"type"`
	DiffIds []string `json:"diff_ids"`
}

type historyEntry struct {
	ID         string
	Size       uint64
	Created    string `json:"created"`
	Author     string `json:"author"`
	CreatedBy  string `json:"created_by"`
	EmptyLayer bool   `json:"empty_layer"`
}

func newConfig(configBytes []byte) config {
	var imageConfig config
	err := json.Unmarshal(configBytes, &imageConfig)
	if err != nil {
		panic(fmt.Errorf("failed to unmarshal docker config: %w", err))
	}

	layerIdx := 0
	for idx := range imageConfig.History {
		if imageConfig.History[idx].EmptyLayer {
			imageConfig.History[idx].ID = "<missing>"
		} else {
			imageConfig.History[idx].ID = imageConfig.RootFs.DiffIds[layerIdx]
			layerIdx++
		}
	}

	return imageConfig
}

func isConfig(configBytes []byte) bool {
	var imageConfig config
	err := json.Unmarshal(configBytes, &imageConfig)

View on GitHub (pinned to d6c691947f)

Solutions

  1. Verify the archive loads with docker itself: docker load -i image.tar - if that also fails, re-save the image
  2. Re-pull and re-save from the source registry to eliminate truncation/corruption
  3. Inspect the config: tar -xf image.tar -O <manifest-listed-config>.json | jq . and compare against a known-good image's config
  4. Update dive - config-struct fixes for newer schema variants land regularly

Example fix

# before
# (archive from an interrupted download)
dive docker-archive://image.tar  # panics: failed to unmarshal docker config

# after
# verify + regenerate the archive
docker pull app:tag && docker save -o image.tar app:tag
dive docker-archive://image.tar
Defensive patterns

Strategy: validation

Validate before calling

// validate the archive's config JSON before handing it to dive
cfgName, err := configNameFromManifest(tarPath) // read manifest.json -> .[0].Config
if err != nil {
    return fmt.Errorf("archive lacks a parseable manifest.json: %w", err)
}
raw, err := fileFromTar(tarPath, cfgName)
if err != nil {
    return fmt.Errorf("config %s missing from archive", cfgName)
}
var probe map[string]json.RawMessage
if err := json.Unmarshal(raw, &probe); err != nil {
    return fmt.Errorf("config JSON is corrupt: %w", err)
}

Try / catch

// newConfig panics; contain it if you call the image_archive package directly:
func safeNewConfig(b []byte) (c docker.Config, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("invalid image config: %v", r)
        }
    }()
    // ... construct via the package's exported path
    return c, nil
}

Prevention

When it happens

Trigger: Feeding a docker-archive whose config JSON is corrupted or truncated (partial docker save, interrupted download); images produced by tools whose config JSON deviates from Docker's schema (unexpected types for history, rootfs.diff_ids, created/author fields); a tar whose *.json file is picked up as the config but is actually something else.

Common situations: Registries/builders that emit slightly different config schemas (older `created` as non-string, extra nesting), hand-assembled or edited archives, and partial downloads - docker itself may tolerate variants that this strict Unmarshal does not.

Related errors


AI-assisted analysis of wagoodman/dive@d6c691947f (2026-08-15). Data as JSON: /api/errors/3d8f83b419807101. Report an issue: GitHub.