Migrating from containerd 1.x: What Changed and How to Check
containerd 1.x is past end of support and Kubernetes has dropped it — what actually breaks moving to 2.x, and how to find out before you upgrade.
containerd 2.0 shipped at the end of 2024, and at the time the project’s own advice was not to rush: 1.x branches would stay supported for a good while longer. That window has now closed. containerd 1.7’s extended support ends in September 2026, and Kubernetes 1.35 is the last Kubernetes release that supports containerd 1.x at all — anything beyond it requires 2.x. If a cluster is still on containerd 1.x, this isn’t an optional upgrade anymore. This guide covers what actually changed and how to check your exposure before you move.
containerd versioned its own API separately from the daemon
Since 2.0, the daemon version and the gRPC API version are no longer the same number: containerd jumped to 2.0, but the API itself made no breaking changes and stayed on a 1.x line (moved to 1.8). The Go client library also moved to its own package path. Practically, this means most direct API or Go-client integrations need nothing more than updated import paths, not a rewrite — the containerd maintainers reported at least one downstream tool’s migration was exactly that, a search-and-replace on import paths.
What was actually removed in 2.0
- Docker schema 1 image support, disabled by default. Schema 1 was superseded by schema 2 in 2014 and then by the OCI image format in 2017; if an image is more than a few years old and hasn’t been rebuilt, it’s a schema 1 candidate. containerd converts schema 1 images on pull rather than storing them as-is, so the practical fix for an image you still need is to pull it once and push it back to your registry, which performs the conversion for you.
- CRI v1alpha2 API support. Kubernetes itself moved to the stable CRI v1 API back in the 1.23 release; any modern kubelet already uses v1. What’s actually at risk here is third-party tooling, monitoring agents, security scanners, anything else that talks to the CRI socket directly, that may not have been updated. Anything bind-mounting the containerd socket or running privileged is worth checking first.
- The two oldest shim implementations (
io.containerd.runtime.v1.linuxandio.containerd.runc.v1), leavingio.containerd.runc.v2as the only one. This had already been the default for years, so most clusters see no behavior change, just a removal of code paths nothing was using. - The
aufssnapshotter, which depended on a non-upstream kernel filesystem.overlayfs, the long-standing default, is the replacement and needs no action if you weren’t deliberately configured for aufs. - io_uring-based syscalls, denied by default following kernel CVEs associated with them. Affected workloads should fall back to older syscall paths for the same functionality rather than failing outright, though containerd can’t detect this ahead of time the way it can the other changes here.
Separately, the old dlopen-style Go plugin mechanism and the legacy CRI registry mirror/config options were deprecated in 2.0 and removed in subsequent 2.x releases, on a similar rationale: low real-world usage, with a clear replacement available (hosts.toml-style registry configuration for the latter).
Find out what you’re actually exposed to
containerd includes a deprecation service that logs a warning, with a timestamp, the first time anything on a node actually exercises a feature slated for removal. It only fires on real usage, not speculatively, so a clean report means a clean upgrade:
ctr deprecations list
ctr deprecations list --format json # for feeding into other tooling
This is a 1.x feature (backported to the 1.6 and 1.7 patch releases), not a 2.0 one, which is exactly what makes it useful: check it on your current cluster, on the runtime you’re about to move off of, before you upgrade anything. containerd clears this state on daemon restart, so if you’ve already restarted containerd or rebuilt nodes recently, run workloads for a while first to let real usage patterns surface again.
A practical upgrade sequence
- Get onto the latest 1.7.x patch release first, so the deprecation service is accurate and you’re not conflating an upgrade with unrelated bug fixes.
- Run production traffic long enough for real usage patterns to surface, then check
ctr deprecations listacross your fleet. - Remediate anything that shows up: convert or replace schema 1 images, update tooling still calling CRI v1alpha2, check config for deprecated registry syntax (most of this migrates automatically; the old syntax keeps working under a 2.x daemon during the transition).
- Re-run and confirm the deprecation list comes back clean.
- Upgrade a test cluster or a subset of nodes to 2.x first, and only then roll out broadly.
If you’re running on a managed Kubernetes provider, check whether it surfaces these deprecation warnings itself — some providers propagate them into node conditions or their own console, and factor them into automated upgrade gating, which removes the need to SSH into every node individually.
Where to look when something doesn’t fit here
The containerd project publishes a full table of every 2.0 change and its replacement, alongside detailed release notes, at containerd.io and in the project’s GitHub repository. For anything this guide doesn’t cover, that’s the authoritative source, along with the CNCF Slack’s #containerd channel for direct help.
Popular Guides
-
From Docker to containerd: A Short History of Kubernetes Runtimes Why Kubernetes stopped talking to Docker directly, and what containerd actually is in relation to it. -
Runtime Internals: cgroups, runc, and Where containerd Fits What actually isolates and limits a container, from the kernel feature up to the daemon Kubernetes talks to. -
containerd's CLI Tools: ctr, nerdctl, and crictl Three command-line tools, three different jobs — which one to reach for and when.