Last updated at 2022/04/26
Initially created at 2022/04/26

containerd's gogo/protobuf migration

containerd has migrated from to in April 2022. This document describes the effort.


containerd has been using gogo/protobuf since the very beginning (TODO: when?). While I wasn't involving the original decision, using gogo/protobuf seemed popular at that time. For example, the following project are/were using gogo/protobuf;

Compared to Google's original Go Protocol Buffers package, gogo/protobuf was much performant and allowed more customization regarding code generation.

However, the maintainers of gogo/protobuf decided to step down and the project is looking for new maintainers since May 2020.

Unfortunately, the personal circumstances of the maintainers have changed and we are no longer able to keep up with the issues and feature requests that naturally crop up for a popular open source project.

In particular, the recent golang/protobuf release 1.4.x (AKA APIv2:, has created a big chunk of work required to be compatible with the new world of Go protobufs.

While taking the ownership was technically possible, maintaining the RPC package was not really containerd's focus. So I started the migration effort in 2021 (TODO: links).


containerd is using Protobuild that wraps protoc-gen-go and/or other code generators that take .proto files. Previously protoc-gen-go was supporting gRPC through its "plugin" mechanism, but the design has been changed since (TODO: version). Now protoc-gen-go is only responsible for Protocol Buffers, and gRPC needs protoc-gen-go-grpc.

Supporting multiple generators was lacking in Protobuild. I extended Protobuild to support protoc-gen-go and protoc-gen-go-grpc.

In addition to that, protoc-gen-go doesn't allow much customization compared to gogo/protobuf. I had to write a small utility program that rewrites Go's AST.


ttrpc is containerd's own RPC protocol, which is basically "gRPC without HTTP".

I have removed gogo/protobuf from ttrpc, and written a new code generator that could be used with protoc-gen-go.


containerd has been using a lot of gogo/protobuf extensions. I have removed all of them in the following PRs. Luckily containerd was converting these auto-generated structs to internal domain models right after getting the messages. The blast radius of the changes was much smaller than I initially expected.

Because of the PRs above, the size of the final migration PR was manageable (TODO: size).


containerd is depending on containerd/imgcrypt and imgcrypt is depending on containerd. In order to workaround the cyclic dependencies, I had to use Go's reflect package.


Went Well

Needs Improvements