traefik/traefik
Patterns and conventions
Cross-cutting conventions used throughout the Go code. Mirroring these makes review faster.
Configuration types
Almost every public type with json, yaml, toml tags is part of the configuration surface. Two rules dominate:
Every field has tags for every supported source format. Look at any struct under
pkg/config/staticorpkg/config/dynamic— every field carriesjson:"…,omitempty",yaml:"…,omitempty",toml:"…,omitempty",description:"…", and oftenexport:"true"orlabel:"allowEmpty".EntryPoints EntryPoints `description:"Entry points definition." json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`description:is documentation. It is used to generate the CLI help, the env-var help, and the public documentation underdocs/content/reference/. Don't write throwaway descriptions — they ship.
When you add a field, also:
- Provide a sensible zero-value default (
SetDefaultsmethod, seepkg/config/dynamic/middlewares.go). - Run
make generateto refresh the documentation reference. - Run
make generate-crdif the type is part of a CRD schema.
Logging
The logger of choice is github.com/rs/zerolog. Conventions:
- Get a context-scoped logger with
log := log.Ctx(ctx)(or uselogs.Logger(ctx)frompkg/observability/logs). - Build structured fields with
.Str("router", name).Str("entryPoint", ep). - Use the
Msgfform only when you need a format string; prefer.Strfields so logs are queryable. - Reserve
Errorlevel for conditions that need attention;Warnfor recoverable misconfiguration;Infofor lifecycle events;Debugfor the configuration loop and per-request hooks.
Fmt'd errors are wrapped with fmt.Errorf("...: %w", err) and logged once at the boundary that decides what to do with them.
Errors
Three patterns recur:
- Provider/middleware constructors return
(handler, error). The error includes context (fmt.Errorf("creating compress middleware: %w", err)). - Lifecycle errors propagate upward from
Server.Startto the top ofrunCmd. - Per-request errors at HTTP boundaries are wrapped in helpers (
pkg/middlewares/recovery,pkg/middlewares/customerrors).
Goroutines
Goroutines are launched through pkg/safe, not directly. Two helpers:
safe.Go(func() {...})for fire-and-forget goroutines that should not panic the process.safe.Poolfor context-aware goroutines whose lifetime is tied to the server. The pool is the one parameter handed into virtually every starting function (Server.Start,ConfigurationWatcher.Start, providerProvide).
Anything that listens forever or polls externally goes through a safe.Pool. This is what allows a single srv.routinesPool.Stop() to take the process down cleanly.
Concurrency state
- Per-provider configuration messages flow over channels.
dynamic.Messageis the unit of exchange. - The "currently active" router tree is swapped atomically by entry points (
pkg/server/server_entrypoint_tcp.gouses*atomic.Value-style swapping under the covers; the public surface isSwitchRouter). - Read-mostly state (TLS certificate store, runtime configuration) is guarded by
sync.RWMutex(pkg/tls/tlsmanager.go,pkg/config/runtime).
Naming
- Files are lowercase, words run together:
routerfactory.go,configurationwatcher.go,handler_http.go. Avoid kebab-case in Go file names. - Exported types use the noun form of the concept:
Router,Service,Middleware,EntryPoint,Provider,Configuration. - Internal helpers stay lowercase. Tests live next to the code (
foo.go/foo_test.go).
Provider conventions
A new provider implements the small interface in pkg/provider/provider.go:
type Provider interface {
Init() error
Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error
}Per-provider conventions seen across pkg/provider/*:
- Each provider has a
Nameconstant (e.g.docker.DockerName,crd.ProviderName). - Each provider has its own logger context:
log.With().Str("providerName", Name).Logger().WithContext(ctx). - Polling providers use
backofffromcenkalti/backoff/v4to retry transient errors. - Watch-style providers (Kubernetes, Docker events, Consul watch) reconnect indefinitely on failure.
Middleware conventions
Each middleware lives in its own subpackage of pkg/middlewares/<name>. The standard shape:
type fooMiddleware struct {
next http.Handler
name string
// …config…
}
func New(ctx context.Context, next http.Handler, cfg dynamic.Foo, name string) (http.Handler, error) {
// validate cfg
return &fooMiddleware{next: next, name: name, /* … */}, nil
}
func (m *fooMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { /* … */ }
func (m *fooMiddleware) GetTracingInformation() (string, string, trace.SpanKind) {
return m.name, typeName, trace.SpanKindInternal
}The GetTracingInformation method is consumed by pkg/middlewares/observability to label spans. The middleware factory is registered with pkg/server/middleware/middlewares.go.
Deprecation
Use // Deprecated: doc comments on fields and methods. Example, from pkg/config/static/static_config.go:
// Core configures Traefik core behavior.
type Core struct {
// Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax.
DefaultRuleSyntax string `description:"…"`
}Deprecated fields keep working until the next major version. The tcli.DeprecationLoader (pkg/cli) emits a warning at startup when a deprecated field is set.
Public API stability
There is no public Go API to honor — Traefik is a binary. Tests under pkg/.../*_test.go are part of the codebase, not part of an API contract. Refactor freely as long as the configuration surface (and its generated docs) stays stable.
What gets shipped
The Go binary embeds:
- The dashboard (
webui/static, viawebui/embed.go). - The error page templates (
pkg/middlewares/customerrors). - Generated JSON schemas for CRDs and Gateway API conformance fixtures.
Everything else — configuration files, plugins, certificates — is loaded from disk or providers at runtime.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.