From Command Line to JSON: Booting Caddy
This module traces caddy run from the Cobra command tree in cmd/main.go down through cmdRun, showing how a config file — Caddyfile or otherwise — gets read, optionally adapted, and handed to caddy.Load as JSON. It shows that config adapters like the Caddyfile's ServerType.Setup are the real translators into Caddy's native JSON format, and that JSON, not Caddyfile, is what Caddy's runtime actually understands.
cmdRun: from CLI invocation to a running config
cmd/commandfuncs.go · lines 188–289cmdRun's very first act is trapping OS signals (SIGINT, SIGTERM, SIGUSR1 for reload, etc.), before any config is even read — so a slow or failing config load never leaves Caddy unable to shut down cleanly.
The run command's flags are read here: --config/--adapter select what to load and how to interpret it, --resume prefers the autosave file over --config, --watch enables live reload, and --pidfile/--pingback support external process supervision.
Unless --resume already produced a config, LoadConfig(configFlag, configAdapterFlag) does the real work: read the file (or stdin), and — if it isn't already JSON — run it through a config adapter before returning.
The PID file is written now, before caddy.Load runs — a deliberate fix for issue #5477 — so external tooling can find the process even if adapting a large config takes a while.
If a config file was actually used, SetLastConfig remembers it and stores a closure that knows how to reload and re-adapt it — this is what lets a running Caddy process reload from the same source file later, e.g. on SIGUSR1.
Finally, the adapted (or already-JSON) bytes are handed to caddy.Load — the single entry point every config, from any source or format, funnels through to actually construct and start apps like the HTTP server explored in earlier modules.
func cmdRun(fl Flags) (int, error) { caddy.TrapSignals() // set up buffered logging for early startup // so that we can hold onto logs until after // the config is loaded (or fails to load) // so that we can write the logs to the user's // configured output. we must be sure to flush // on any error before the config is loaded. logger, defaultLogger, logBuffer := caddy.BufferedLog() undoMaxProcs := setResourceLimits(logger) defer undoMaxProcs() // release the local reference to the undo function so it can be GC'd; // the deferred call above has already captured the actual function value. undoMaxProcs = nil //nolint:ineffassign,wastedassign configFlag := fl.String("config") configAdapterFlag := fl.String("adapter") resumeFlag := fl.Bool("resume") printEnvFlag := fl.Bool("environ") watchFlag := fl.Bool("watch") pidfileFlag := fl.String("pidfile") pingbackFlag := fl.String("pingback") // load all additional envs as soon as possible err := handleEnvFileFlag(fl) if err != nil { logBuffer.FlushTo(defaultLogger) return caddy.ExitCodeFailedStartup, err } // if we are supposed to print the environment, do that first if printEnvFlag { printEnvironment() } // load the config, depending on flags var config []byte if resumeFlag { config, err = os.ReadFile(caddy.ConfigAutosavePath) if errors.Is(err, fs.ErrNotExist) { // not a bad error; just can't resume if autosave file doesn't exist logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) resumeFlag = false } else if err != nil { logBuffer.FlushTo(defaultLogger) return caddy.ExitCodeFailedStartup, err } else { if configFlag == "" { logger.Info("resuming from last configuration", zap.String("autosave_file", caddy.ConfigAutosavePath)) } else { // if they also specified a config file, user should be aware that we're not // using it (doing so could lead to data/config loss by overwriting!) logger.Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration", zap.String("autosave_file", caddy.ConfigAutosavePath)) } } } // we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive var configFile string var adapterUsed string if !resumeFlag { config, configFile, adapterUsed, err = LoadConfig(configFlag, configAdapterFlag) if err != nil { logBuffer.FlushTo(defaultLogger) return caddy.ExitCodeFailedStartup, err } } // create pidfile now, in case loading config takes a while (issue #5477) if pidfileFlag != "" { err := caddy.PIDFile(pidfileFlag) if err != nil { logger.Error("unable to write PID file", zap.String("pidfile", pidfileFlag), zap.Error(err)) } } // If we have a source config file (we're running via 'caddy run --config ...'), // record it so SIGUSR1 can reload from the same file. Also provide a callback // that knows how to load/adapt that source when requested by the main process. if configFile != "" { caddy.SetLastConfig(configFile, adapterUsed, func(file, adapter string) error { cfg, _, _, err := LoadConfig(file, adapter) if err != nil { return err } return caddy.Load(cfg, true) }) } // run the initial config err = caddy.Load(config, true) if err != nil { logBuffer.FlushTo(defaultLogger) return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) } // release the reference to the config so it can be GC'd config = nil //nolint:ineffassign,wastedassign
// load config adapter if adapterName != "" { cfgAdapter = caddyconfig.GetAdapter(adapterName) if cfgAdapter == nil { return nil, "", "", fmt.Errorf("unrecognized config adapter: %s", adapterName) } } // adapt config if cfgAdapter != nil { adaptedConfig, warnings, err := cfgAdapter.Adapt(config, map[string]any{ "filename": configFile, }) if err != nil { return nil, "", "", fmt.Errorf("adapting config using %s: %v", adapterName, err) } logger.Info("adapted config to JSON", zap.String("adapter", adapterName)) for _, warn := range warnings { msg := warn.Message if warn.Directive != "" { msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message) } logger.Warn(msg, zap.String("adapter", adapterName), zap.String("file", warn.File), zap.Int("line", warn.Line)) } config = adaptedConfig
cmd/main.go · lines 202–229Check your understanding
Answered in place — nothing is graded, everything is explained. 0 / 2 passed
What actually determines whether `caddy run` sends a config file through a config adapter before loading it?
In cmdRun, why is the PID file written before caddy.Load runs, rather than after the server is confirmed up?