A batteries-included go-kratos monorepo template for shipping production microservices fast. Clone it, rename one app, and you've got proto-driven gRPC + HTTP APIs, Wire DI, Dapr secrets/pubsub, a durable Dapr Workflow example, a one-command Tilt dev loop with live reload and Delve, and integration tests against real Redis via testcontainers.
- Layered architecture —
service → biz → data, DDD-friendly, infra primitives only indata/ - Proto-first APIs — gRPC + HTTP + OpenAPI generated from
api/**/*.proto - Wire for compile-time dependency injection
- Dapr sidecar for secret injection (
secretstore), event publishing (pubsub), and durable workflow state (statestore) - Dapr Workflow worked example — durable, replayable orchestration of activities
- Tilt for local Kubernetes dev — live reload + remote Delve on
:7000 - testcontainers-go integration tests (real Redis, no infra mocks)
- Devcontainer with all toolchains pinned (Go, kratos, protoc, wire, kubectl, helm, dapr, tilt, gh, claude-code)
git clone https://github.com/<you>/<your-repo>.git
cd <your-repo>
code . # VS Code → "Reopen in Container"
cp .devcontainer/.env.example .devcontainer/.env # if present, fill in per-developer overridesThe devcontainer mounts the host Docker socket (testcontainers + Tilt builds) and the host kubeconfig (rewritten to host.docker.internal). Bring your own local Kubernetes — Docker Desktop or OrbStack.
make init # protoc plugins, kratos, wire — all pinned
make all # api + config + generate (wire)make dev # tilt up --continue (Delve attaches in background)
# or
make debug # tilt up (Delve waits for VS Code on :7000)Forwards: HTTP :8000, gRPC :9000, Delve :7000, Redis :6379.
# Brew a cup
curl -X POST http://localhost:8000/v1/coffee \
-H 'Content-Type: application/json' \
-d '{"beans":"arabica","size":"large"}'
# => {"instanceId":"..."}The template ships one app called coffee. To make it yours:
- Module name — edit go.mod (
module coffee→module github.com/you/yourapp) and rungo mod tidy. - App directory —
mv app/coffee app/<yourapp>. Update Makefile (./bin/coffee→./bin/<yourapp>) and Tiltfile (./app/coffee/...,dist/coffee). - API namespace —
mv api/coffee api/<yourapp>and update thepackage/option go_packagelines inapi/<yourapp>/**/*.proto. Re-runmake api. - Helm release — rename the chart in deploy/helm/ and the namespace in deploy/k8s/ (default:
coffee). - Dapr app-id — update component
metadata.namespacein deploy/k8s/base/infra/dapr/. - Run
make all && make wire(fromapp/<yourapp>/) and you're done.
To add a second service instead, copy app/coffee/ to app/<newapp>/, copy the proto tree under api/, regenerate, and add a Tilt resource for the new binary.
api/<app>/<domain>/v<N>/ protobuf contracts (source of truth)
├─ *.proto
└─ generated *.pb.go, *_grpc.pb.go, *_http.pb.go
app/<app>/
cmd/server/ entrypoint + Wire
internal/
conf/ config proto → conf.pb.go
server/ HTTP + gRPC bootstrap
service/ proto server impl (transport layer)
biz/ domain logic, workflow lifecycle
data/ infra primitives only (Dapr workflow client)
deploy/
helm/ Redis chart
k8s/ base + overlays (Dapr components live here)
docs/ topic-specific guides
.devcontainer/ pinned toolchain + Docker-out-of-Docker
Dependency rule: service → biz → data. data only provides infra primitives (Dapr clients, etc.); biz owns domain logic and never imports data types beyond those primitives.
| Command | What it does |
|---|---|
make init |
Install pinned protoc plugins, wire |
make api |
Generate Go/gRPC/HTTP + OpenAPI from api/**/*.proto |
make config |
Generate Go structs from internal/conf/*.proto |
make generate |
go generate ./... + go mod tidy (wire) |
make all |
api + config + generate |
make build |
Build binary → ./bin/<app> |
make test |
go test -race ./... (requires Docker) |
make dev |
tilt up --continue |
make debug |
tilt up (Delve waits for debugger) |
| Command | What it does |
|---|---|
make wire |
Regenerate wire_gen.go |
make run |
go run ./cmd/server |
make test |
go test -v ./... |
Static config lives in app/<app>/configs/config.yaml. Secrets are injected at runtime by Dapr — main.go waits up to 60 s for the Dapr sidecar, then loads the secret bundle via internal/conf/secrets.go. Secret keys are declared as struct tags on conf.Secrets and validated non-empty at startup.
Provide secret values via the Dapr secretstore component in deploy/k8s/base/infra/dapr/.
TDD-friendly: write the test first, watch it fail for the right reason, implement the minimum, re-run.
Integration tests use testcontainers-go to spin up real Redis — there are no infra mocks. make test therefore needs Docker running. Helper startRedis(t) lives in app/<app>/internal/data/testhelper_test.go; containers tear down via t.Cleanup.
Use github.com/stretchr/testify/assert and structure each test with AAA comments:
// Arrange
// Act
// AssertSingle test:
cd app/<app> && go test -v -run TestFunctionName ./internal/...- docs/dapr.md — Dapr setup, components, sidecar wiring
- docs/dapr-workflow.md — Coffee workflow walkthrough
- CLAUDE.md — agent-oriented guide to the same architecture
Use it however you like — fork it, rebrand it, ship it.