Open-Source Wikis

/

containerd

/

How to contribute

/

Patterns and conventions

containerd/containerd

Patterns and conventions

This page captures the recurring patterns a contributor needs to know to write code that "looks like" the rest of containerd.

Package and import structure

  • Every Go file starts with the Apache 2.0 license header (matching cmd/containerd/main.go).
  • Imports are grouped: stdlib, then third-party, then github.com/containerd/..., then local module imports. goimports enforces this.
  • Avoid _ and repeated parent-directory words in package names. The package is named after its directory (package server in cmd/containerd/server).
  • Don't add new files at the repository root — the v2 reorg moved everything into core/, pkg/, internal/, etc.

Errors

  • Wrap errors with context using fmt.Errorf("...: %w", err). The codebase consistently passes the inner error through.
  • For sentinel errors that callers compare against, use github.com/containerd/errdefs (e.g. errdefs.ErrNotFound, errdefs.ErrAlreadyExists). The errdefs package maps these to gRPC status codes at the boundary.
  • Don't return raw errors.New(...) for cross-package error contracts; either define a typed error or wrap an errdefs sentinel.

Example (recurring throughout core/):

if err := checkArg(name); err != nil {
    return fmt.Errorf("invalid name %q: %w", name, errdefs.ErrInvalidArgument)
}

Contexts

  • Every public method takes a context.Context first. The context carries the namespace, log fields, and the OpenTelemetry span.
  • Use namespaces.NamespaceRequired(ctx) (in pkg/namespaces) at the top of services that require a namespace.
  • Don't store contexts in structs. Pass them through explicitly.

Logging

  • log.G(ctx).WithFields(...) is the standard form. The G helper attaches the trace ID from the context.
  • Use structured fields rather than fmt.Sprintf into the message: WithField("id", id) instead of Errorf("error for %s", id).
  • Severity rules of thumb: Info for lifecycle (plugin loaded, task started), Warn for recoverable misconfig, Error for unexpected failures.

Plugin registration

A built-in plugin's init() looks like:

func init() {
    registry.Register(&plugin.Registration{
        Type: plugins.SnapshotPlugin,
        ID:   "overlayfs",
        InitFn: func(ic *plugin.InitContext) (any, error) {
            return overlay.NewSnapshotter(ic.Properties[plugins.PropertyRootDir])
        },
    })
}
  • The ID is the plugin's name in config ([plugins."io.containerd.snapshotter.v1.overlayfs"]).
  • Requires enumerates dependencies by plugin type; the plugin manager ensures they're initialized first.
  • Config declares the plugin's TOML config struct. Use plugin.ConfigType so containerd config default knows about it.
  • Skipping initialization (e.g. unsupported platform) should return plugin.ErrSkipPlugin; required plugins error out, optional ones are silently dropped.

Concurrency

  • Long-running goroutines must respect ctx.Done() and propagate cancellation.
  • Locking strategy: prefer sync.RWMutex for read-heavy maps (the metadata cache, plugin registry).
  • The codebase uses errgroup.Group for fan-out work but is restrained about it; new uses should wait on completion before returning.

Filesystem helpers

  • pkg/sys wraps platform-dependent syscalls (MkdirAllWithACL, OpenLockedFile, …). Don't reimplement these.
  • pkg/atomicfile for atomic write-rename.
  • pkg/ioutil for stream helpers (AtomicWriteFile, LimitWriter).
  • core/mount is the only place that manages mounts; never call unix.Mount directly from code outside core/mount.

Cross-platform code

  • _linux.go, _windows.go, _freebsd.go, _darwin.go, _other.go build-tag suffixes are how the codebase splits per-OS code. Keep platform-specific syscalls inside these files.
  • The Makefile drives GOOS=... builds; CI exercises Linux + Windows + (best-effort) FreeBSD/macOS.

Public API stability

  • Anything in api/ is part of the gRPC contract; breaking changes are gated by buf-breaking.yml.
  • Anything in client/ is consumed by external embedders; deprecations follow the rules in RELEASES.md.
  • internal/ is fair game for refactor.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – containerd wiki | Factory