containerd 2.0 in production: real migrations
Table of contents
- Key takeaways
- What 2.0 brings that justifies the jump
- What breaks on migration
- The Docker Swarm case
- Observability during migration
- My read
- Frequently asked questions
- Can I upgrade to containerd 2.0 if my kubelet is older than Kubernetes 1.26?
- What has to change in `/etc/containerd/config.toml` when moving to 2.0?
- Does containerd 2.0 affect a server running Docker Engine or Docker Swarm?
- Sources
Six months after containerd 2.0 reached general availability there is enough real-world mileage to judge the migration from the 1.x branch in production. We cover what changes in the config file, what breaks on Kubernetes and Docker Swarm, and when planning the jump actually pays off.
containerd 2.0 shipped on November 5, 2024, and we’ve had six months to watch how it behaves in real deployments, per the official 2.0.0 release notes[1]. 1.7 is still under extended support, but the 1.6 branch has been out of active support since 2023, with its extended-support window closing in August 2025. That closure forces the teams still on that branch to plan the jump, per the containerd support policy[2]. Enough reports have surfaced since to allow an honest read: what breaks, what improves, and when planning the migration pays off.
Key takeaways
-
The main driver to jump is that 1.6 no longer has support, not that 2.0 brings indispensable features.
-
CRI v1alpha2 is removed: kubelet must be on 1.26+ before migrating Kubernetes clusters.
-
The configuration file format changes:
version = 3instead ofversion = 2. -
OCI Schema 1 image support is removed: images built before 2017 may not run.
-
Docker Swarm migration is gentler than Kubernetes because Swarm doesn’t depend on CRI.
-
Key metrics during migration: active container count, failed-pull ratio, task API latency, and shim errors.
What 2.0 brings that justifies the jump
Beyond internal cleanup, four things have concrete operational value:
-
Native sandbox support. Kata Containers and Firecracker can integrate as runtimes via the sandbox API without external shims. For teams evaluating reinforced isolation this reduces the number of moving parts. Connects directly to what we described in Firecracker: microVMs for multi-tenant services.
-
CRI v1 stable and v1alpha2 removed. Kubernetes 1.26 and later use CRI v1 by default. containerd 2.0 removes the v1alpha2 implementation, simplifying code but breaking compatibility with kubelet older than 1.26.
-
Runtime shim improvements. The runc shim was refactored and uses less memory per container. On a host with hundreds of containers the savings are noticeable: tens of megabytes less total overhead.
-
NRI as stable plugin API. The Node Resource Interface, previously experimental, is now the official interface for integrating custom CNI, device plugins and security agents.
What breaks on migration
It’s rarely a drop-in upgrade. There are at least five places where you need to touch config or scripts:
-
Config file format. The
version = 2clause is no longer recognised; declareversion = 3in/etc/containerd/config.toml. Ansible or Puppet scripts injecting configuration must adjust the template. containerd 2.0 converts version 2 automatically but emits a warning, and 2.1 plans to drop automatic conversion. -
Deregistered plugins. There are plugins like
io.containerd.grpc.v1.introspectionthat changed namespace or name. If external integrations do plugin introspection, review them. -
OCI Schema 1 image support removed. Old images built before 2017 may be Schema 1 and containerd 2.0 won’t run them. Most modern registries convert on pull, but images in old private registries need repackaging.
-
ctrbehaviour on namespace-shared containers. Some commands that previously worked silently across namespaces now require the explicit namespace. Scripts usingctrwithout--namespacefail with clear errors, but if they run in cron or CI you need to update them. -
CNI 0.x deprecation. containerd 2.0 requires CNI plugins 1.0 or higher. If the cluster has Calico 3.17 or pre-0.20 Flannel, upgrade them.
The Docker Swarm case
For those still running Swarm, migration is gentler than on Kubernetes because Swarm doesn’t depend on CRI. What matters here isn’t whether Docker Engine ships containerd 2.0 out of the box, but how it’s wired to the system containerd.
One nuance worth clearing up, since it tripped up the team: Docker Engine still bundles its own static containerd. That copy stayed on the 1.7.x line through the whole 27.x series, per the Docker Engine 27 release notes[3]. What does work, and what we did, is point Docker Engine at the system containerd via the standard socket (/run/containerd/containerd.sock), which is where the Debian package already puts it.
In that setup Docker Engine talks to containerd over the standard gRPC API and doesn’t care whether the version is 1.7 or 2.0 as long as the socket responds. The order we followed was:
-
Update the system’s containerd from 1.7 to 2.0.
-
Restart
containerd.service. -
Verify
ctr versionand that existing containers kept running. -
Confirm Docker Engine, without touching its own version, kept operating normally against the system containerd.
No container drops, but two images had to be rebuilt because they were Schema 1.
Observability during migration
Something I learned the hard way in a previous migration: default containerd doesn’t emit Prometheus metrics without configuration. Before migrating, ensure metrics.address = "0.0.0.0:1338" is set in config and the exporter is scraped. Without that, the only signal something’s wrong is containers dying.
Key metrics to watch during and after migration:
-
Active container count.
-
Failed-pull ratio.
-
Task API latency.
-
Shim errors.
With those four signals covered, a bad migration shows up in minutes instead of hours. The same observe-before-acting principle applies to Kubernetes 1.32 operations management.
My read
containerd 2.0 is a mandatory but not urgent migration when there is no regulatory pressure. The main driver is that 1.6 no longer has active support. If the cluster is on 1.7 with no regulatory pressure, jumping straight to 2.1 tends to be safer than sitting on the first wave of 2.0. That release landed in May 2025, with the first stabilization patches.
What I find most positive about the release is the cleanup. Removing CRI v1alpha2 and OCI Schema 1 support, documented in the official containerd 2.0 upgrade guide[4], are decisions the community postponed for years and that will pay maintainability dividends. The CRI v1 requirement is also confirmed in the Kubernetes CRI architecture docs[5]: since 1.26, the kubelet requires the v1 API to register the node.
My practical recommendation:
-
If you run Kubernetes: check kubelet on every node before touching anything, upgrade Calico or your CNI to 1.0, and plan a maintenance window with rollback.
-
If you run Swarm: simpler, but watch old images.
-
If you run plain docker without Swarm or Kubernetes: migration is nearly transparent as long as you keep the system containerd’s lifecycle separate from Docker Engine’s.
This article is also available in Spanish: containerd 2.0 en producción: migraciones reales.
Frequently asked questions
Can I upgrade to containerd 2.0 if my kubelet is older than Kubernetes 1.26?
Not without upgrading kubelet first: containerd 2.0 removes the CRI v1alpha2 implementation and only offers CRI v1, which kubelet uses by default from 1.26 and requires to register the node. Before touching anything, check the kubelet version on every node. Upgrade the CNI to plugins 1.0 or higher (Calico 3.17 or pre-0.20 Flannel will not do) and plan a maintenance window with rollback. If you are on 1.7 with no pressure, jumping straight to 2.1 from May 2025 tends to be safer.
What has to change in `/etc/containerd/config.toml` when moving to 2.0?
Declare version = 3 instead of version = 2, and adjust the Ansible or Puppet templates that inject that configuration. containerd 2.0 converts version 2 automatically with a warning, but 2.1 plans to drop that conversion. It is also worth adding metrics.address = "0.0.0.0:1338" and scraping the exporter, because default containerd emits no Prometheus metrics and without them the only failure signal is containers dying. Scripts using ctr without --namespace will fail and need updating.
Does containerd 2.0 affect a server running Docker Engine or Docker Swarm?
Less than Kubernetes, because Swarm does not depend on CRI: Docker Engine still bundles its own static containerd on the 1.7.x line through the whole 27.x series. What works is pointing Docker Engine at the system containerd via the /run/containerd/containerd.sock socket: it speaks standard gRPC and does not care whether 1.7 or 2.0 answers. The order was to update containerd, restart containerd.service, verify ctr version and confirm Docker kept operating. No containers dropped, but two Schema 1 images had to be rebuilt.