Caddy — Structure#

Layout pattern#

Custom (Root-as-Library + cmd/ entry point + modules/ plugin tree)

Caddy does not follow the standard Go layout where code lives under pkg/. Instead, the root of the repository is the core library package (github.com/caddyserver/caddy/v2). All foundational types — App, Context, Module, Config, AdminRouter, Replacer, Storage — live in root-level .go files. A cmd/ directory holds the CLI entry point, internal/ holds private helpers, and a modules/ tree holds all pluggable functionality. There is no pkg/ directory at all. This is a deliberate design: the root package is the stable public API; everything else extends it.

Directory map#

caddy/                          ← Core framework package (caddy.go, admin.go, modules.go, etc.)
├── cmd/                        ← CLI implementation
│   ├── main.go                 ← package caddycmd: all CLI commands, config loading, flags
│   └── caddy/
│       └── main.go             ← package main: binary entry point (3 imports + main())
├── caddyconfig/                ← Config adapter system
│   ├── configadapters.go       ← Adapter interface + registry
│   ├── load.go / httploader.go ← Config loading over HTTP admin API
│   ├── caddyfile/              ← Caddyfile lexer, parser, formatter, dispenser
│   └── httpcaddyfile/          ← HTTP-specific Caddyfile adapter (routes, handlers, matchers)
├── caddytest/                  ← Integration test harness
│   └── integration/            ← End-to-end integration tests + Caddyfile adapt tests
├── internal/                   ← Private packages
│   ├── filesystems/            ← fs.FS utilities
│   ├── metrics/                ← Prometheus metrics helpers
│   └── testmocks/              ← Mock types for testing
├── modules/                    ← All pluggable Caddy modules
│   ├── standard/               ← Import aggregator for standard module bundle
│   ├── caddyevents/            ← Event system module
│   │   └── eventsconfig/       ← Event handler config
│   ├── caddyfs/                ← Filesystem module
│   ├── caddyhttp/              ← HTTP server module (largest sub-tree)
│   │   ├── caddyauth/          ← HTTP authentication handlers
│   │   ├── encode/             ← Response compression (gzip, brotli, zstd sub-packages)
│   │   ├── fileserver/         ← Static file serving
│   │   ├── headers/            ← Request/response header manipulation
│   │   ├── intercept/          ← Response interception
│   │   ├── logging/            ← Access logging
│   │   ├── map/                ← Map handler (config-driven value mapping)
│   │   ├── proxyprotocol/      ← PROXY protocol listener wrapper
│   │   ├── push/               ← HTTP/2 server push
│   │   ├── requestbody/        ← Request body limits
│   │   ├── reverseproxy/       ← Reverse proxy + load balancer
│   │   │   ├── fastcgi/        ← FastCGI upstream
│   │   │   └── forwardauth/    ← Forward auth handler
│   │   ├── rewrite/            ← URI rewriting
│   │   ├── standard/           ← Import aggregator for standard HTTP modules
│   │   ├── templates/          ← Server-side template rendering
│   │   └── tracing/            ← OpenTelemetry tracing middleware
│   ├── caddypki/               ← Built-in CA (local PKI)
│   │   └── acmeserver/         ← Embedded ACME server
│   ├── caddytls/               ← TLS automation (CertMagic integration)
│   │   ├── distributedstek/    ← Distributed session ticket key management
│   │   └── standardstek/       ← Standard (local) STEK rotation
│   ├── filestorage/            ← File-based cert/data storage
│   ├── internal/network/       ← Internal network utilities for modules
│   ├── logging/                ← Log sink modules (file, net, stderr)
│   └── metrics/                ← Prometheus metrics module
└── notify/                     ← OS service readiness notifications (systemd sd_notify)

Entry points#

FileBinaryPurpose
cmd/caddy/main.gocaddySole production binary. Intentionally minimal: imports caddycmd.Main() and modules/standard (blank import pulls in all standard modules). Designed to be trivially copied and customized by users.

There is no cmd/main.go binary; cmd/main.go is package caddycmd — the full CLI implementation (subcommands: run, start, stop, reload, adapt, validate, fmt, upgrade, add-package, list-modules, environ, version, manpage, completion, etc.).

Package organization#

Internal packages (internal/):

  • internal/filesystemsfs.FS utility types used by the file server and other modules
  • internal/metrics — shared Prometheus metrics helpers, used by both the root metrics.go and modules/metrics
  • internal/testmocks — mock caddy.Storage and connection types for unit tests

Public packages (no pkg/ dir; root + modules are all public):

  • Root package (github.com/caddyserver/caddy/v2) — the core framework API: App, Context, Config, Module, AdminRouter, Replacer, Storage, Logger, Provisioner, Validator, etc.
  • cmd — CLI command framework; re-exported as caddycmd for embedding Caddy in other programs
  • caddyconfig — config adapter registry and loader; caddyconfig/caddyfile for the Caddyfile DSL; caddyconfig/httpcaddyfile for HTTP-specific Caddyfile-to-JSON translation
  • caddytest — public test harness for module authors to write integration tests
  • modules/caddyhttp — HTTP server, matchers, handlers, routes, response writers
  • modules/caddytls — TLS automation; STEK rotation; session resumption
  • modules/caddypki — local CA with embedded ACME server
  • modules/reverseproxy — full-featured reverse proxy and load balancer
  • modules/logging, modules/metrics — observability modules

Layering:

[ cmd/caddy/main.go ]          ← binary: just imports
       ↓
[ cmd/ (caddycmd) ]            ← CLI: config loading, lifecycle commands
       ↓
[ caddyconfig/ ]               ← config parsing & adaptation layer
       ↓
[ root package (caddy) ]       ← core: module registry, config engine, admin API, context
       ↓
[ modules/* ]                  ← plugins: register themselves via init() → caddy.RegisterModule()
       ↓
[ internal/* ]                 ← private helpers: no imports of caddy (dependency-free)

The layering is clean: modules depend on the root package, not on each other (with minor exceptions like reverseproxy importing caddyhttp types). The modules/standard and modules/caddyhttp/standard packages are pure import aggregators with no code — they exist only to pull in module init() functions.

Build system#

  • Build tool: GoReleaser (.goreleaser.yml) for releases; plain go build for development
  • Key targets:
    • go build ./cmd/caddycaddy binary (all standard modules included)
    • GoReleaser produces cross-platform binaries for darwin, linux, windows, freebsd × amd64, arm, arm64, s390x, ppc64le, riscv64
    • CGO_ENABLED=0 throughout (pure Go, static binaries)
    • Build tags: nobadger, nomysql, nopgx in release builds to exclude optional storage backends
    • -trimpath -mod=readonly -s -w are standard release flags
  • Release peculiarity: GoReleaser copies cmd/caddy/main.go into a scratch caddy-build/ directory (gitignored) and builds from there. This keeps the working tree clean during a tagged release, since go mod edit would otherwise dirty the module files.
  • xcaddy: The canonical tool for user-customized builds. Users copy cmd/caddy/main.go, add plugin imports, and run xcaddy build. This is how third-party modules are distributed.
  • Docker: No Dockerfile in the main repo; Docker images are maintained separately in caddyserver/dist.
  • Packaging: GoReleaser produces .deb packages with systemd service files, man pages, and bash completions sourced from caddyserver/dist.
  • SBOM + signing: GoReleaser pipeline generates CycloneDX SBOMs (via syft) and signs all artifacts with cosign.

Notable structural decisions#

  1. Root package is the public API. Unlike most Go projects that bury library code under pkg/, Caddy’s root github.com/caddyserver/caddy/v2 is the stable, versioned library surface. This means anyone embedding Caddy imports the root package directly — no extra path indirection.

  2. cmd/caddy/main.go is a deliberate template. At 43 lines with 3 imports and a single main() call, the entry point is intentionally trivial to copy and modify. The comment block inside is a step-by-step guide for building custom Caddy binaries. The actual CLI complexity lives in cmd/ (package caddycmd), which is importable by third-party programs via caddycmd.Main().

  3. Import-aggregator standard packages. Both modules/standard/imports.go and modules/caddyhttp/standard/imports.go contain nothing but blank imports. This pattern avoids forcing users to enumerate all modules they want; they just import the bundle. It also makes the “what’s in the default build” completely explicit and auditable from a single file.

  4. Platform-specific files at root level. The root package has many OS/arch-specific files (listen_unix.go, filepath_windows.go, service_windows.go, sigtrap_posix.go, sigtrap_nonposix.go, listen_unix_setopt_freebsd.go, etc.). This is unusual — most projects consolidate platform logic in subpackages — but makes sense here since the root package owns the listener and OS signal handling abstractions.

  5. modules/internal/ — a private subpackage inside modules. The modules/internal/network/ package is internal relative to the modules/ subtree, meaning only code under modules/ can import it. This uses Go’s internal visibility rules at sub-tree granularity, demonstrating awareness of fine-grained encapsulation.

  6. No vendoring in normal dev, but vendored for releases. The .goreleaser.yml runs go mod vendor as the first build step. This ensures reproducible release builds and allows bundling vendor in the source tarball archive, while keeping the dev workflow clean (no committed vendor/).