🌐 Works Anywhere
Unlike remote plugin systems, Bufrnix runs completely offline. Perfect for corporate firewalls, air-gapped environments, or unreliable internet connections.
Bufrnix makes it easy to integrate Protocol Buffers into your Nix-based projects with declarative configuration and reproducible builds. This comprehensive guide will walk you through setting up Bufrnix, generating code for your first Protocol Buffer definitions, and avoiding common pitfalls.
Before diving into setup, it’s worth understanding why Bufrnix exists and when it’s the right choice for your project:
🌐 Works Anywhere
Unlike remote plugin systems, Bufrnix runs completely offline. Perfect for corporate firewalls, air-gapped environments, or unreliable internet connections.
🔒 Keeps Schemas Private
All processing happens locally - your proprietary API definitions never leave your environment. Essential for regulated industries.
⚡ Blazing Fast
Up to 60x faster than remote alternatives. No network latency, no rate limiting, no timeouts. Parallel execution across languages.
🎯 Truly Reproducible
Same inputs = identical outputs, always. Cryptographic hashes ensure supply chain integrity across all environments.
🔧 No Technical Limits
Break free from 64KB response constraints, enable plugin chaining, file system access, and custom multi-stage workflows.
💰 Cost Effective
Access the full community plugin ecosystem without Pro/Enterprise subscriptions. Develop custom plugins freely.
Perfect for Bufrnix
Good for Buf Remote
Best of Both Worlds
Many teams use both tools together:
Ready to get started? Let’s build your first Bufrnix project.
If you haven’t enabled flakes yet, add this to your Nix configuration:
# For NixOS, add to /etc/nixos/configuration.nix:nix.settings.experimental-features = [ "nix-command" "flakes" ];
# For non-NixOS systems, add to ~/.config/nix/nix.conf:experimental-features = nix-command flakes
Create your project directory and navigate to it
Create a flake.nix
file in your project root
Add the Bufrnix configuration from the example below
{inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; bufrnix.url = "github:conneroisu/bufrnix"; bufrnix.inputs.nixpkgs.follows = "nixpkgs";};
outputs = { nixpkgs, bufrnix, ... }:let system = "x86_64-linux"; # Adjust for your system pkgs = nixpkgs.legacyPackages.${system};in { packages.default = bufrnix.lib.mkBufrnixPackage { inherit (pkgs) lib pkgs; config = { root = ./.; protoc.files = ["./proto/example/v1/example.proto"]; languages.go = { enable = true; outputPath = "gen/go"; grpc.enable = true; }; }; };
devShells.default = pkgs.mkShell { packages = with pkgs; [ go protobuf buf grpcurl ]; };};}
{inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; bufrnix.url = "github:conneroisu/bufrnix"; bufrnix.inputs.nixpkgs.follows = "nixpkgs";};
outputs = { nixpkgs, bufrnix, ... }:let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system};in { packages.default = bufrnix.lib.mkBufrnixPackage { inherit (pkgs) lib pkgs; config = { root = ./.; protoc.files = ["./proto/**/*.proto"]; languages = { go = { enable = true; outputPath = "gen/go"; grpc.enable = true; }; js = { enable = true; outputPath = "src/proto"; es.enable = true; }; dart = { enable = true; outputPath = "lib/proto"; grpc.enable = true; }; }; }; };
devShells.default = pkgs.mkShell { packages = with pkgs; [ go nodejs dart protobuf buf grpcurl ]; };};}
Create a directory structure for your Protocol Buffer definitions:
Add your first proto file at proto/example/v1/example.proto
:
syntax = "proto3";
package example.v1;
option go_package = "github.com/yourorg/yourproject/gen/go/example/v1;examplev1";
// Simple greeting service demonstrating various protobuf featuresservice GreetingService { // Standard unary RPC rpc SayHello(HelloRequest) returns (HelloResponse);
// Server streaming RPC rpc SayHelloStream(HelloRequest) returns (stream HelloResponse);
// Client streaming RPC rpc SayManyHellos(stream HelloRequest) returns (HelloResponse);
// Bidirectional streaming RPC rpc SayHelloBidi(stream HelloRequest) returns (stream HelloResponse);}
// The request message containing the user's namemessage HelloRequest { string name = 1; optional string greeting_type = 2; repeated string languages = 3;
// Nested message example UserInfo user_info = 4;}
// Nested message demonstrating message compositionmessage UserInfo { string email = 1; int32 age = 2; UserType type = 3;}
// Enum exampleenum UserType { USER_TYPE_UNSPECIFIED = 0; USER_TYPE_REGULAR = 1; USER_TYPE_PREMIUM = 2; USER_TYPE_ADMIN = 3;}
// The response message containing the greetingmessage HelloResponse { string message = 1; int64 timestamp = 2; bool success = 3;
// Using well-known types google.protobuf.Duration processing_time = 4;}
Run the code generation:
nix build
This will:
buf
(if enabled)gen/go/example/v1/example.pb.go
gen/go/example/v1/example_grpc.pb.go
The generated Go code can be used like this:
package main
import ( "context" "fmt" "log" "net" "time"
"google.golang.org/grpc" "google.golang.org/protobuf/types/known/durationpb" examplev1 "github.com/yourorg/yourproject/gen/go/example/v1")
// Server implementationtype server struct { examplev1.UnimplementedGreetingServiceServer}
func (s *server) SayHello(ctx context.Context, req *examplev1.HelloRequest) (*examplev1.HelloResponse, error) { start := time.Now()
greeting := "Hello" if req.GreetingType != nil { greeting = *req.GreetingType }
// Handle optional user info var userSuffix string if req.UserInfo != nil { userSuffix = fmt.Sprintf(" (User: %s, Type: %s)", req.UserInfo.Email, req.UserInfo.Type.String()) }
// Handle repeated languages field langSuffix := "" if len(req.Languages) > 0 { langSuffix = fmt.Sprintf(" in languages: %v", req.Languages) }
return &examplev1.HelloResponse{ Message: greeting + ", " + req.Name + "!" + userSuffix + langSuffix, Timestamp: time.Now().Unix(), Success: true, ProcessingTime: durationpb.New(time.Since(start)), }, nil}
func (s *server) SayHelloStream(req *examplev1.HelloRequest, stream examplev1.GreetingService_SayHelloStreamServer) error { for i := 0; i < 5; i++ { response := &examplev1.HelloResponse{ Message: fmt.Sprintf("Stream message %d for %s", i+1, req.Name), Timestamp: time.Now().Unix(), Success: true, }
if err := stream.Send(response); err != nil { return err }
time.Sleep(1 * time.Second) } return nil}
func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) }
s := grpc.NewServer() examplev1.RegisterGreetingServiceServer(s, &server{})
log.Println("Server listening at :50051") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) }}
Bufrnix supports generating code for multiple languages simultaneously. Here’s an extended configuration:
config = { root = ./.; protoc = { sourceDirectories = ["./proto"]; includeDirectories = ["./proto"]; files = [ "./proto/example/v1/example.proto" "./proto/user/v1/user.proto" ]; };
# Go with full ecosystem languages.go = { enable = true; outputPath = "gen/go"; options = ["paths=source_relative"]; grpc = { enable = true; options = ["paths=source_relative"]; }; gateway = { enable = true; # HTTP/JSON gateway options = ["paths=source_relative"]; }; validate = { enable = true; # Message validation options = ["lang=go"]; }; connect = { enable = true; # Modern Connect protocol options = ["paths=source_relative"]; }; };
# Dart for Flutter apps languages.dart = { enable = true; outputPath = "lib/proto"; packageName = "my_app_proto"; grpc.enable = true; };
# JavaScript/TypeScript for web languages.js = { enable = true; outputPath = "src/proto"; packageName = "my-proto"; es = { enable = true; # Modern ES modules options = ["target=ts"]; # Generate TypeScript }; connect = { enable = true; # Connect-ES options = ["target=ts"]; }; grpcWeb = { enable = true; # Browser gRPC options = ["import_style=typescript" "mode=grpcwebtext"]; }; };
# PHP for web services languages.php = { enable = true; outputPath = "gen/php"; namespace = "MyApp\\Proto"; twirp.enable = true; };};
Create a development environment with all necessary tools:
{ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; bufrnix.url = "github:conneroisu/bufrnix"; bufrnix.inputs.nixpkgs.follows = "nixpkgs"; };
outputs = { nixpkgs, bufrnix, ... }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { # Code generation package packages.default = bufrnix.lib.mkBufrnixPackage { inherit (pkgs) lib pkgs; config = { /* your config */ }; };
# Development shell with language runtimes devShells.default = pkgs.mkShell { packages = with pkgs; [ # Language runtimes for your generated code go dart nodejs php83
# Protocol Buffer tools protobuf protoc-gen-go protoc-gen-go-grpc
# Development tools buf # Modern protobuf linter and build tool grpcurl # Command-line gRPC client protoc-gen-grpc-web protoc-gen-connect-go
# Optional: language-specific tools golangci-lint gopls ];
shellHook = '' echo "🚀 Bufrnix development environment loaded!" echo "Available commands:" echo " nix build - Generate protobuf code" echo " buf lint - Lint proto files" echo " buf format - Format proto files" echo " grpcurl - Test gRPC services" ''; }; };}
Enter the development environment:
nix develop
# Generate code from proto filesnix build
# Enter development shell with all toolsnix develop
# After making changes to .proto files, regenerate codenix build
# Lint proto files (if buf is in your devShell)buf lint
# Format proto filesbuf format --write
# Test gRPC services (after running your generated server)grpcurl -plaintext localhost:50051 listgrpcurl -plaintext -d '{"name": "World"}' localhost:50051 example.v1.GreetingService/SayHello
For simple Go-only projects:
config = { root = ./.; protoc.files = ["./proto/**/*.proto"]; languages.go = { enable = true; outputPath = "internal/proto"; grpc.enable = true; };};
For microservices architectures:
config = { root = ./.; protoc = { sourceDirectories = ["./proto"]; includeDirectories = [ "./proto" "${pkgs.protobuf}/include" # Well-known types "${pkgs.googleapis-common-protos}/share/googleapis-common-protos" # Google APIs ]; };
languages.go = { enable = true; outputPath = "gen/go"; packagePrefix = "github.com/myorg/myproject"; grpc = { enable = true; options = ["require_unimplemented_servers=false"]; }; gateway = { enable = true; options = ["generate_unbound_methods=true"]; }; validate = { enable = true; options = ["lang=go"]; }; };
debug = { enable = true; # Enable for troubleshooting verbosity = 2; };};
For projects with both client and server code:
{ packages = { # Server-side code with full gRPC server = bufrnix.lib.mkBufrnixPackage { inherit (pkgs) lib pkgs; config = { root = ./.; protoc.files = ["./proto/**/*.proto"]; languages.go = { enable = true; outputPath = "server/proto"; grpc.enable = true; gateway.enable = true; }; }; };
# Client-side code for web client = bufrnix.lib.mkBufrnixPackage { inherit (pkgs) lib pkgs; config = { root = ./.; protoc.files = ["./proto/**/*.proto"]; languages.js = { enable = true; outputPath = "client/src/proto"; es.enable = true; grpcWeb.enable = true; }; }; }; };}
Bufrnix includes several complete examples you can study and modify:
# Clone the repository to explore examplesgit clone https://github.com/conneroisu/bufrnix.gitcd bufrnix
# Try the simple Go examplecd examples/simple-flakenix developgo run main.go
# Try the comprehensive Dart examplecd ../dart-examplenix developdart pub getdart run lib/main.dart
# Try the JavaScript examplecd ../js-examplenix developnpm install && npm run build
# Try the PHP Twirp examplecd ../php-twirpnix developcomposer install
Error:
Error: proto file not found: ./proto/example/v1/example.proto
Solutions:
protoc.files
matches your actual file locationroot
directoryError:
Import "google/protobuf/timestamp.proto" was not found
Solution: Add well-known types to your include directories:
protoc.includeDirectories = [ "./proto" "${pkgs.protobuf}/include"];
Error:
package github.com/yourorg/yourproject/gen/go/example/v1 is not in GOROOT or GOPATH
Solutions:
go_package
option matches your actual Go module structurego mod tidy
after generationgo.mod
file includes the correct module pathError:
error: builder for '/nix/store/...' failed with exit code 1
Troubleshooting steps:
debug = { enable = true; verbosity = 3;};
Error:
Permission denied: cannot write to gen/go/
Solutions:
Error:
error: a 'x86_64-linux' with features {} is required to build...
Solution: Ensure your system
variable matches your actual system:
let # Use the correct system identifier # x86_64-linux, x86_64-darwin, aarch64-linux, aarch64-darwin system = builtins.currentSystem; # or specify explicitly
Use specific file lists instead of globbing for faster builds:
protoc.files = [ "./proto/user/v1/user.proto" "./proto/product/v1/product.proto"];
Enable parallel builds in your Nix configuration:
# Add to ~/.config/nix/nix.confmax-jobs = auto
Use Nix binary caches to avoid rebuilding dependencies:
# Add to ~/.config/nix/nix.confsubstituters = https://cache.nixos.org https://nix-community.cachix.orgtrusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
Now that you have Bufrnix generating code:
If you encounter issues not covered in this guide:
Remember that Bufrnix leverages the power of Nix for reproducible builds, so builds that work on one machine should work identically on all machines with the same Nix configuration.