# How the Caddy Context System Works: Module Lifecycle and Dependency Injection Explained

> Understand the Caddy context system module lifecycle and dependency injection. Learn how Caddy manages configuration and graceful cleanup across its server architecture.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: internals
- Published: 2026-03-03

---

**The Caddy context system is a thin wrapper around Go's `context.Context` that manages module lifecycles, dependency injection, configuration access, and graceful cleanup across the Caddy server architecture.**

The context system in `caddyserver/caddy` serves as the backbone for the web server’s modular architecture. It ties together configuration loading, module provisioning, logging, and resource management into a cohesive framework. Understanding how this system operates is essential for developing custom Caddy modules or debugging server behavior.

## Understanding the Context Structure

The `Context` type defined in [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) extends Go’s standard context with Caddy-specific state management capabilities.

### Core Fields and Their Purposes

The struct definition at lines 46-55 of [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) reveals the internal architecture:

```go
type Context struct {
    context.Context
    moduleInstances map[string][]Module
    cfg             *Config
    ancestry        []Module
    cleanupFuncs    []func()
    exitFuncs       []func(context.Context)
    metricsRegistry *prometheus.Registry
}

```

**Key components include:**

- **Embedded `context.Context`** – Provides standard cancellation signals and value storage.
- **`moduleInstances`** – Tracks every module instantiated within this context for deterministic cleanup.
- **`cfg`** – Holds the active `*Config` containing apps, storage, and logging configuration.
- **`ancestry`** – Maintains a stack of provisioned modules, enabling `ctx.Module()` to identify the current module.

## Creating and Managing Context Lifecycles

Context creation follows a strict lifecycle protocol that ensures proper initialization and guaranteed cleanup.

### The NewContext Function

The `NewContext` function at lines 57-89 of [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go) constructs child contexts:

```go
func NewContext(ctx Context) (Context, context.CancelFunc)

```

**The initialization process:**

1. Copies the parent’s configuration ([`ctx.cfg`](https://github.com/caddyserver/caddy/blob/main/ctx.cfg)) into the new context.
2. Creates a fresh `moduleInstances` map to isolate module tracking.
3. Wraps the parent’s `ctx.Context` with `context.WithCancel`.
4. Returns a `wrappedCancel` function that orchestrates shutdown sequences.

### Cancellation and Cleanup Hooks

When `wrappedCancel` executes, it triggers a deterministic cleanup sequence:

1. Calls the underlying `context.CancelFunc`.
2. Executes all functions registered via `ctx.OnCancel`.
3. Iterates through `moduleInstances` and invokes `Cleanup()` on any module implementing the `CleanerUpper` interface.

Register cleanup functions using:

```go
func (ctx Context) OnCancel(f func())

```

For graceful shutdown scenarios, use the experimental exit hook:

```go
func (ctx Context) OnExit(f func(context.Context))

```

## Loading and Provisioning Modules

The context system serves as the primary entry point for Caddy’s dynamic module loading architecture.

### LoadModule and LoadModuleByID

Caddy provides three public entry points for module instantiation:

**`LoadModule(structPtr any, fieldName string) (any, error)`**

Reflects on a struct field, reads its `caddy` struct tag, parses the module namespace, and dispatches to the appropriate loader. Located at lines 81-79 of [`context.go`](https://github.com/caddyserver/caddy/blob/main/context.go).

**`LoadModuleByID(id string, rawMsg json.RawMessage) (any, error)`**

Low-level loader that:
- Looks up the module ID in the global `modules` registry from [`modules.go`](https://github.com/caddyserver/caddy/blob/main/modules.go).
- Constructs the module via its `New()` function.
- Unmarshals raw JSON configuration.
- Executes `Provision(ctx)` and `Validate()` if the module implements those interfaces.

**`loadModuleInline(key, scope string, raw json.RawMessage)`**

Helper for inline-key modules where the module name appears inside the JSON object, such as `{"handler": "file_server", ...}`.

### Module Registration and Ancestry Tracking

During loading, the context performs several bookkeeping operations:

- **Module tracking** – Adds the module to `ctx.moduleInstances[id]`.
- **App registration** – If the module is an `App`, stores it in `ctx.cfg.apps` for retrieval via `ctx.App()`.
- **Ancestry management** – Pushes the module onto `ctx.ancestry`, enabling subsequent calls to identify the provisioning chain.

Access the current module lineage using:

```go
ctx.Module()   // Returns the most recently provisioned module
ctx.Modules()  // Returns a copy of the full ancestry slice

```

## Practical Usage Patterns

### Accessing Loggers and Configuration

The context provides convenience methods for logging that automatically attribute entries to the current module:

```go
func (ctx Context) Logger(module ...Module) *zap.Logger
func (ctx Context) Slogger() *slog.Logger

```

If no module is specified, `Logger()` uses `ctx.Module()`. When running outside of a configured environment (such as in tests), these methods create development loggers automatically.

Retrieve application modules using:

```go
app, err := ctx.App("http")           // Loads or creates the HTTP app
app, err := ctx.AppIfConfigured("tls") // Errors if TLS app isn't configured

```

### Registering Cleanup Functions

Modules that spawn background goroutines or hold resources must register cleanup logic:

```go
func (m *MyModule) Provision(ctx caddy.Context) error {
    // Start background work
    go m.runBackgroundWork(ctx)
    
    // Register cleanup to stop the goroutine when context cancels
    ctx.OnCancel(func() { close(m.stopCh) })
    return nil
}

```

For graceful shutdown scenarios where you need access to the shutdown context:

```go
ctx.OnExit(func(shutdownCtx context.Context) {
    // Perform graceful shutdown with timeout context
})

```

## Summary

- The **Caddy context system** wraps Go’s standard `context.Context` to provide module lifecycle management, dependency injection, and resource cleanup.
- **Context creation** via `NewContext()` establishes isolated module registries and configures deterministic cleanup through `wrappedCancel`.
- **Module loading** happens through `LoadModule()`, `LoadModuleByID()`, and inline loaders, which automatically execute `Provision()` and `Validate()` lifecycle hooks.
- **Ancestry tracking** maintains a stack of provisioned modules, enabling `ctx.Module()` to identify the current module for logging and configuration scoping.
- **Cleanup management** through `OnCancel()` and `OnExit()` ensures goroutines, file handles, and network connections release properly when configurations reload or the server shuts down.

## Frequently Asked Questions

### How does Caddy's context system differ from Go's standard context?

While Go’s `context.Context` provides cancellation signals and request-scoped values, Caddy’s `Context` struct embeds it and adds **module lifecycle management**, **configuration access**, and **automatic resource cleanup**. The Caddy version tracks every instantiated module in `moduleInstances`, maintains provisioning ancestry, and executes cleanup hooks when the context cancels, which standard Go contexts do not handle.

### What is the module ancestry stack used for?

The **ancestry slice** in `ctx.ancestry` records the order in which modules are provisioned during configuration loading. This stack enables the `ctx.Module()` method to return the most recently provisioned module, which logging systems use to attribute log entries to the correct component. It also allows modules to understand their position in the dependency hierarchy during the provisioning phase.

### How do I properly clean up resources when a Caddy module reloads?

Implement the **`CleanerUpper`** interface and register explicit cleanup functions using **`ctx.OnCancel()`**. When Caddy reloads its configuration, it cancels the old context, triggering the `wrappedCancel` sequence: it calls your `OnCancel` functions, then invokes `Cleanup()` on any module implementing `CleanerUpper`. For graceful shutdown scenarios where you need a timeout context, use `ctx.OnExit()` instead.

### Can I access other Caddy apps from within my module?

Yes, use **`ctx.App(name)`** to retrieve or initialize another application module by name (such as `"http"` or `"tls"`). This method looks up the app in `ctx.cfg.apps` and returns the configured instance. If you want to ensure the app is actually configured before retrieving it, use **`ctx.AppIfConfigured(name)`**, which returns an error if the specified app isn't present in the current configuration.