Why Hand‑Typing protoc in 2026? Switch to Buf CLI for Modern Go + Protobuf

The article explains how the traditional protoc‑based Go protobuf workflow suffers from environment hell, path nightmares, and lack of linting, and demonstrates step‑by‑step how Buf CLI—combined with its built‑in lint, breaking‑change detection, and optional Schema Registry—replaces protoc with a declarative, reproducible, and cloud‑native toolchain.

TonyBai
TonyBai
TonyBai
Why Hand‑Typing protoc in 2026? Switch to Buf CLI for Modern Go + Protobuf

Go together with Protocol Buffers (Protobuf) and gRPC has become the de‑facto standard for high‑performance microservices, but the code‑generation part has lagged behind the modern Go ecosystem. The author shows why the native protoc workflow is painful and why the community has converged on Buf (buf.build) as the preferred solution.

Core pain points of the native protoc workflow

Projects that still use protoc typically contain a Makefile or build.sh script similar to the following:

# Traditional Makefile snippet
.PHONY: generate-proto

PROTO_FILES=$(shell find api -name "*.proto")

generate-proto:
	@echo "Generating Go code from Protobuf..."
	protoc \
	 -I api \
	 -I /usr/local/include \
	 -I $(GOPATH)/pkg/mod/github.com/grpc-ecosystem/[email protected]/third_party/googleapis \
	 --go_out=gen/go \
	 --go_opt=paths=source_relative \
	 --go-grpc_out=gen/go \
	 --go-grpc_opt=paths=source_relative \
	 $(PROTO_FILES)

This approach introduces three major "sins":

Environment dependency hell – every developer and CI container must have the C++ protoc binary and matching versions of protoc-gen-go and protoc-gen-go-grpc installed in $PATH. Version mismatches cause subtle code differences and merge conflicts.

Path‑import maze – protoc is file‑system based. Adding third‑party imports (e.g., import "google/api/annotations.proto") requires manually constructing long -I (or --proto_path) arguments.

Lack of normative enforcement – protoc does not check naming conventions (snake_case vs. camelCase) or protect against breaking changes. Developers end up debugging runtime deserialization crashes.

These issues motivate the search for a modern replacement.

Buf CLI: a declarative modern Protobuf toolchain

Buf is a Go‑written, all‑in‑one compiler suite that replaces the protoc binary and its plugins. Its design philosophy includes:

Declarative configuration – a concise buf.yaml replaces tangled shell commands.

Consistency guarantees – identical generation results on any machine or CI environment.

Built‑in engineering features – linting and breaking‑change detection are first‑class citizens of the CLI.

Step 1: Install Go and Buf CLI

Ensure Go 1.22+ is installed, then download the pre‑built Buf binary (or use go install with the official script):

# Download buf CLI v1.66.0 for Linux x86_64
PREFIX="/usr/local"
VERSION="1.66.0"
curl -sSL "https://github.com/bufbuild/buf/releases/download/v${VERSION}/buf-$(uname -s)-$(uname -m).tar.gz" | \
  tar -xvzf - -C "${PREFIX}" --strip-components 1

buf --version
# → 1.66.0

Step 2: Create project structure and write Protobuf IDL

Initialize a Go module and create a proto directory:

$ mkdir -p acme-shop && cd acme-shop
$ go mod init github.com/acme/shop

Place the service definition in proto/acme/order/v1/order.proto:

// proto/acme/order/v1/order.proto
syntax = "proto3";

package acme.order.v1;

option go_package = "github.com/acme/shop/gen/go/acme/order/v1;orderv1";

import "google/protobuf/timestamp.proto";

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {}
}

message CreateOrderRequest {
  string customer_id = 1;
  double amount = 2;
}

message CreateOrderResponse {
  string order_id = 1;
  google.protobuf.Timestamp created_at = 2;
}

Step 3: Initialise Buf module (buf.yaml)

Run buf mod init inside the proto directory. This creates a minimal buf.yaml:

version: v2
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

The file tells Buf that the root import path is proto/ and automatically enables linting and breaking‑change checks.

Step 4: Zero‑config lint

Introduce a naming violation by changing customer_id to customerId:

message CreateOrderRequest {
  // Violates the recommended snake_case naming
  string customerId = 1;
  double amount = 2;
}

Run the lint command:

$ buf lint proto
proto/acme/order/v1/order.proto:18:10: Field name "customerId" should be lower_snake_case, such as "customer_id".

Fix the name and re‑run – no output means the check passes.

Step 5: Declarative code generation (buf.gen.yaml)

Create buf.gen.yaml to declare the Go and gRPC plugins:

version: v1
plugins:
  - plugin: go
    out: gen/go
    opt: paths=source_relative
  - plugin: go-grpc
    out: gen/go
    opt: paths=source_relative

Step 6: One‑click generation

Execute:

$ buf generate proto

The command is idempotent; the generated files appear under gen/go/acme/order/v1/:

$ tree -F gen/go
gen/go/
└── acme/
    └── order/
        └── v1/
            ├── order.pb.go
            └── order_grpc.pb.go

Step 7: Breaking‑change protection

Modify the field number of amount from 2 to 3 (a backward‑incompatible change):

message CreateOrderRequest {
  string customer_id = 1;
  // Dangerous: changed field number
  double amount = 3;
}

Run the breaking check against a previous snapshot (or the main branch):

$ buf breaking proto --against proto_backup
proto/acme/order/v1/order.proto:17:1: Previously present field "2" with name "amount" on message "CreateOrderRequest" was deleted.

Integrate this command into CI pipelines (GitHub Actions, GitLab CI) to prevent accidental API incompatibilities.

Deep dive: Buf Schema Registry (BSR)

BSR is a SaaS platform that hosts thousands of public Protobuf modules. By declaring a remote dependency in buf.yaml:

version: v1
deps:
  - buf.build/googleapis/googleapis

and running buf mod update, the required .proto files are fetched automatically, eliminating manual vendoring of files such as google/api/annotations.proto.

Remote plugins

BSR also offers cloud‑executed plugins, allowing teams to drop all local protoc‑gen‑* binaries. The buf.gen.yaml points to remote plugin images:

version: v1
plugins:
  - plugin: buf.build/protocolbuffers/go:v1.36.11
    out: gen/go
    opt: paths=source_relative
  - plugin: buf.build/grpc/go:v1.6.1
    out: gen/go
    opt: paths=source_relative

Running buf generate proto now sends the .proto payload to BSR’s compile cluster, which returns the generated .pb.go files directly to the local gen/go directory. This guarantees identical compiler versions for every developer and can even work without any local protoc‑gen‑* binaries.

Conclusion and outlook

In 2026 the Go ecosystem has moved past hand‑written protoc scripts. The three takeaways are:

Discard Makefile‑based protoc workflows – they lack reproducibility and safety.

Adopt Buf CLI ( buf mod init, buf lint, buf breaking) as the standard Protobuf engineering foundation, whether used locally or with BSR.

Embrace remote module and plugin management (BSR) to offload dependency and version‑control burdens, representing the future direction of system‑level application development.

By switching to Buf, teams gain deterministic builds, built‑in linting, breaking‑change detection, and optional cloud‑native compilation, dramatically improving developer experience and API contract reliability.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

clicode generationmicroservicesgRPCprotobufbsrbuf
TonyBai
Written by

TonyBai

Tony Bai's tech world (tonybai.com). Not satisfied with just "knowing how", we strive for mastery. Focused on Go language internals, high-quality engineering practices, and cloud‑native architecture, exploring cutting‑edge intersections of Go and AI. Gophers who pursue technology are welcome—follow me and evolve with Go.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.