xcaddy

package module
v0.4.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 21 Imported by: 2

README

xcaddy - Custom Caddy Builder

This command line tool and associated Go package makes it easy to make custom builds of the Caddy Web Server.

It is used heavily by Caddy plugin developers as well as anyone who wishes to make custom caddy binaries (with or without plugins).

Stay updated, be aware of changes, and please submit feedback! Thanks!

Requirements

Install

You can download binaries that are already compiled for your platform from the Release tab.

You may also build xcaddy from source:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

For Debian, Ubuntu, and Raspbian, an xcaddy package is available from our Cloudsmith repo:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-xcaddy.list
sudo apt update
sudo apt install xcaddy

⚠ Pro tip

If you find yourself fighting xcaddy in relation to your custom or proprietary build or development process, it might be easier to just build Caddy manually!

Caddy's main.go file, the main entry point to the application, has instructions in the comments explaining how to build Caddy essentially the same way xcaddy does it. But when you use the go command directly, you have more control over the whole thing and it may save you a lot of trouble.

The manual build procedure is very easy: just copy the main.go into a new folder, initialize a Go module, plug in your plugins (add an import for each one) and then run go build. Of course, you may wish to customize the go.mod file to your liking (specific dependency versions, replacements, etc).

Command usage

The xcaddy command has two primary uses:

  1. Compile custom caddy binaries
  2. A replacement for go run while developing Caddy plugins

The xcaddy command will use the latest version of Caddy by default. You can customize this for all invocations by setting the CADDY_VERSION environment variable.

As usual with go command, the xcaddy command will pass the GOOS, GOARCH, and GOARM environment variables through for cross-compilation.

Note that xcaddy will ignore the vendor/ folder with -mod=readonly.

Custom builds

Syntax:

$ xcaddy build [<caddy_version>]
    [--output <file>]
    [--with <module[@version][=replacement]>...]
    [--replace <module[@version]=replacement>...]
    [--embed <[alias]:path/to/dir>...]
    [--pgo <file>] # EXPERIMENTAL
  • <caddy_version> is the core Caddy version to build; defaults to CADDY_VERSION env variable or latest.
    This can be the keyword latest, which will use the latest stable tag, or any git ref such as:

    • A tag like v2.0.1
    • A branch like master
    • A commit like a58f240d3ecbb59285303746406cab50217f8d24
  • --output changes the output file.

  • --with can be used multiple times to add plugins by specifying the Go module name and optionally its version, similar to go get. Module name is required, but specific version and/or local replacement are optional.

  • --replace is like --with, but does not add a blank import to the code; it only writes a replace directive to go.mod, which is useful when developing on Caddy's dependencies (ones that are not Caddy modules). Try this if you got an error when using --with, like cannot find module providing package.

  • --embed can be used to embed the contents of a directory into the Caddy executable. --embed can be passed multiple times with separate source directories. The source directory can be prefixed with a custom alias and a colon : to write the embedded files into an aliased subdirectory, which is useful when combined with the root directive and sub-directive.

  • --pgo can be used to specify a file containing a profile to use for profile guided optimization. If a file named default.pgo is present in the current directory, it will automatically be used. This feature is new to xcaddy and is considered experimental.

Examples
$ xcaddy build \
    --with github.com/caddyserver/ntlm-transport

$ xcaddy build v2.0.1 \
    --with github.com/caddyserver/ntlm-transport@v0.1.1

$ xcaddy build master \
    --with github.com/caddyserver/ntlm-transport

$ xcaddy build a58f240d3ecbb59285303746406cab50217f8d24 \
    --with github.com/caddyserver/ntlm-transport

$ xcaddy build \
    --with github.com/caddyserver/ntlm-transport=../../my-fork

$ xcaddy build \
    --with github.com/caddyserver/ntlm-transport@v0.1.1=../../my-fork

You can even replace Caddy core using the --with flag:

$ xcaddy build \
    --with github.com/caddyserver/caddy/v2=../../my-caddy-fork
    
$ xcaddy build \
    --with github.com/caddyserver/caddy/v2=github.com/my-user/caddy/v2@some-branch

This allows you to hack on Caddy core (and optionally plug in extra modules at the same time!) with relative ease.


If --embed is used without an alias prefix, the contents of the source directory are written directly into the root directory of the embedded filesystem within the Caddy executable. The contents of multiple unaliased source directories will be merged together:

$ xcaddy build --embed ./my-files --embed ./my-other-files
$ cat Caddyfile
{
	# You must declare a custom filesystem using the `embedded` module.
	# The first argument to `filesystem` is an arbitrary identifier
	# that will also be passed to `fs` directives.
	filesystem my_embeds embedded
}

localhost {
	# This serves the files or directories that were
	# contained inside of ./my-files and ./my-other-files
	file_server {
		fs my_embeds
	}
}

You may also prefix the source directory with a custom alias and colon separator to write the source directory's contents to a separate subdirectory within the embedded filesystem:

$ xcaddy build --embed foo:./sites/foo --embed bar:./sites/bar
$ cat Caddyfile
{
	filesystem my_embeds embedded
}

foo.localhost {
	# This serves the files or directories that were
	# contained inside of ./sites/foo
	root * /foo
	file_server {
		fs my_embeds
	}
}

bar.localhost {
	# This serves the files or directories that were
	# contained inside of ./sites/bar
	root * /bar
	file_server {
		fs my_embeds
	}
}

This allows you to serve 2 sites from 2 different embedded directories, which are referenced by aliases, from a single Caddy executable.


If you need to work on Caddy's dependencies, you can use the --replace flag to replace it with a local copy of that dependency (or your fork on github etc if you need):

$ xcaddy build some-branch-on-caddy \
    --replace golang.org/x/net=../net
For plugin development

If you run xcaddy from within the folder of the Caddy plugin you're working on without the build subcommand, it will build Caddy with your current module and run it, as if you manually plugged it in and invoked go run.

The binary will be built and run from the current directory, then cleaned up.

The current working directory must be inside an initialized Go module.

Syntax:

$ xcaddy [--] <args...>
  • <args...> are passed through to the caddy command. This is not the place for xcaddy build flags such as --with.
  • Use -- before Caddy flags (such as --config) so that xcaddy does not try to parse them as its own flags. Without the separator, commands like xcaddy run --config caddy.json fail with unknown flag: --config.

For example:

$ xcaddy list-modules
$ xcaddy run
$ xcaddy -- run --config caddy.json
Building with your plugin plus other plugins

The development shortcut above only includes the module in the current directory. Flags like --with belong to xcaddy build. If you put them on a bare xcaddy / xcaddy run invocation, they are passed through to Caddy and fail with unknown flag: --with.

To produce a binary that includes your local plugin and one or more other plugins, use xcaddy build with multiple --with flags. Point your own module at the current directory with =.:

# from inside your plugin's module directory
$ xcaddy build \
    --with github.com/me/my-plugin=. \
    --with github.com/mholt/caddy-events-exec

$ ./caddy run

You can add as many --with plugins as you need. After the build finishes, run the resulting binary (default ./caddy) yourself — same as any other custom build.

The race detector can be enabled by setting XCADDY_RACE_DETECTOR=1. The DWARF debug info can be enabled by setting XCADDY_DEBUG=1.

Getting xcaddy's version
$ xcaddy version

Library usage

builder := xcaddy.Builder{
	CaddyVersion: "v2.0.0",
	Plugins: []xcaddy.Dependency{
		{
			ModulePath: "github.com/caddyserver/ntlm-transport",
			Version:    "v0.1.1",
		},
	},
}
err := builder.Build(context.Background(), "./caddy")

Versions can be anything compatible with go get.

Environment variables

Because the subcommands and flags are constrained to benefit rapid plugin prototyping, xcaddy does read some environment variables to take cues for its behavior and/or configuration when there is no room for flags.

  • CADDY_VERSION sets the version of Caddy to build.
  • XCADDY_RACE_DETECTOR=1 enables the Go race detector in the build.
  • XCADDY_DEBUG=1 enables the DWARF debug information in the build.
  • XCADDY_SETCAP=1 will run sudo setcap cap_net_bind_service=+ep on the resulting binary. By default, the sudo command will be used if it is found; set XCADDY_SUDO=0 to avoid using sudo if necessary.
  • XCADDY_SKIP_BUILD=1 causes xcaddy to not compile the program, it is used in conjunction with build tools such as GoReleaser. Implies XCADDY_SKIP_CLEANUP=1.
  • XCADDY_SKIP_CLEANUP=1 causes xcaddy to leave build artifacts on disk after exiting.
  • XCADDY_WHICH_GO sets the go command to use when for example more then 1 version of go is installed.
  • XCADDY_GO_BUILD_FLAGS overrides default build arguments. Supports Unix-style shell quoting, for example: XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'". The provided flags are applied to go commands: build, clean, get, install, list, run, and test
  • XCADDY_GO_MOD_FLAGS overrides default go mod arguments. Supports Unix-style shell quoting.
  • XCADDY_PRINT_VERSION prints the Caddy version after building to prove the build is working (defaults to 1).

© 2020 Matthew Holt

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder struct {
	Compile
	CaddyVersion string        `json:"caddy_version,omitempty"`
	Plugins      []Dependency  `json:"plugins,omitempty"`
	Replacements []Replace     `json:"replacements,omitempty"`
	TimeoutGet   time.Duration `json:"timeout_get,omitempty"`
	TimeoutBuild time.Duration `json:"timeout_build,omitempty"`
	RaceDetector bool          `json:"race_detector,omitempty"`
	SkipCleanup  bool          `json:"skip_cleanup,omitempty"`
	SkipBuild    bool          `json:"skip_build,omitempty"`
	Debug        bool          `json:"debug,omitempty"`
	BuildFlags   string        `json:"build_flags,omitempty"`
	ModFlags     string        `json:"mod_flags,omitempty"`
	PgoProfile   string        `json:"pgo_profile,omitempty"` // Experimental

	// Experimental: subject to change
	EmbedDirs []struct {
		Dir  string `json:"dir,omitempty"`
		Name string `json:"name,omitempty"`
	} `json:"embed_dir,omitempty"`

	// OnStep, if set, is called for each build step, making the
	// build's progress observable and controllable. The callback
	// runs in its own goroutine, concurrently with the step it
	// describes, so it can consume the event's Output directly,
	// with no extra goroutine of its own:
	//
	//	OnStep: func(e *xcaddy.StepEvent) error {
	//		scanner := bufio.NewScanner(e.Output)
	//		for scanner.Scan() {
	//			showProgress(e.Step, scanner.Text())
	//		}
	//		return scanner.Err()
	//	}
	//
	// Callbacks never overlap: the next step does not begin until
	// the previous step's callback has returned, and Build does
	// not return until the final callback has returned. Output
	// reaches EOF when the step ends, so a callback that reads
	// until EOF returns naturally at the step boundary; one that
	// lingers afterward delays the build accordingly.
	//
	// Returning a non-nil error aborts the build at the end of
	// the step: no further steps begin, and the error is returned
	// from Build, wrapped with the step name (use errors.Is or
	// errors.As to match a custom error). To cancel mid-step,
	// cancel the context passed to Build instead. Aborting does
	// not skip removal of the temporary build folder. The
	// callback must not call back into the Builder.
	OnStep func(event *StepEvent) error `json:"-"`

	// Env, if non-nil, is used as the entire environment of the
	// underlying go commands, in KEY=value form (see os.Environ);
	// the process's own environment is not inherited. This
	// enables hermetic, credential-free builds: if build logs are
	// published (see OnStep), no ambient credential (GOPROXY
	// userinfo, git configuration, .netrc, cloud keys, ...) can
	// leak into them when the build never had access to any. The
	// environment must include everything the go toolchain needs,
	// at minimum PATH and HOME (or GOCACHE and GOMODCACHE); GOOS,
	// GOARCH, GOARM and CGO_ENABLED are set by the Builder
	// itself. If nil, the process environment is inherited, which
	// is the historical behavior.
	Env []string `json:"-"`

	// Secrets lists sensitive values (tokens, passwords, keys)
	// that must not appear in step output: every occurrence is
	// replaced with "[REDACTED]" before it reaches a StepEvent's
	// Output. Matching is exact and line-buffered, so a secret
	// cannot slip through by straddling a write boundary; values
	// containing newlines cannot be matched. Secrets has no
	// effect when OnStep is nil.
	Secrets []string `json:"-"`

	// RedactCredentials additionally masks common credential
	// shapes in step output: userinfo in URLs, well-known token
	// formats (GitHub, GitLab, Slack, AWS access key IDs), and
	// PEM-encoded private key blocks. It is a best-effort
	// backstop for publishable logs, not a guarantee; prefer
	// building in a credential-free environment (see Env). It has
	// no effect when OnStep is nil.
	RedactCredentials bool `json:"-"`
}

Builder can produce a custom Caddy build with the configuration it represents.

func (Builder) Build

func (b Builder) Build(ctx context.Context, outputFile string) (err error)

Build builds Caddy at the configured version with the configured plugins and plops down a binary at outputFile.

type Compile added in v0.1.2

type Compile struct {
	Platform
	Cgo bool `json:"cgo,omitempty"`
}

Compile contains parameters for compilation.

func SupportedPlatforms added in v0.1.2

func SupportedPlatforms() ([]Compile, error)

SupportedPlatforms runs `go tool dist list` to make a list of possible build targets.

func (Compile) CgoEnabled added in v0.1.4

func (c Compile) CgoEnabled() string

CgoEnabled returns "1" if c.Cgo is true, "0" otherwise. This is used for setting the CGO_ENABLED env variable.

type Dependency

type Dependency struct {
	// The name (import path) of the Go package. If at a version > 1,
	// it should contain semantic import version (i.e. "/v2").
	// Used with `go get`.
	PackagePath string `json:"module_path,omitempty"`

	// The version of the Go module, as used with `go get`.
	Version string `json:"version,omitempty"`
}

Dependency pairs a Go module path with a version.

func (Dependency) String added in v0.4.2

func (d Dependency) String() string

type Platform added in v0.1.2

type Platform struct {
	OS   string `json:"os,omitempty"`
	Arch string `json:"arch,omitempty"`
	ARM  string `json:"arm,omitempty"`
}

Platform represents a build target.

type Replace added in v0.1.1

type Replace struct {
	// The import path of the module being replaced.
	Old ReplacementPath `json:"old,omitempty"`

	// The path to the replacement module.
	New ReplacementPath `json:"new,omitempty"`
}

Replace represents a Go module replacement.

func NewReplace added in v0.1.6

func NewReplace(old, new string) Replace

NewReplace creates a new instance of Replace provided old and new Go module paths

type ReplacementPath added in v0.1.6

type ReplacementPath string

ReplacementPath represents an old or new path component within a Go module replacement directive.

func (ReplacementPath) Param added in v0.1.6

func (r ReplacementPath) Param() string

Param reformats a go.mod replace directive to be compatible with the `go mod edit` command.

func (ReplacementPath) String added in v0.1.6

func (r ReplacementPath) String() string

type Step added in v0.4.7

type Step string

Step identifies a phase of the build process. Steps are reported to a Builder's OnStep callback, if set.

const (
	StepCreateEnvironment Step = "create_environment" // set up the temporary build module
	StepInitializeModule  Step = "initialize_module"  // go mod init and module replacements
	StepPinVersions       Step = "pin_versions"       // go get Caddy and plugins
	StepWindowsResources  Step = "windows_resources"  // generate Windows version resources
	StepTidyModule        Step = "tidy_module"        // go mod tidy
	StepCompile           Step = "compile"            // go build
	StepCleanup           Step = "cleanup"            // remove the temporary folder
)

The build steps, in the order they occur. StepWindowsResources only occurs when targeting Windows; StepTidyModule and StepCompile do not occur if SkipBuild is enabled; and StepCleanup does not occur if SkipCleanup is enabled.

type StepEvent added in v0.4.7

type StepEvent struct {
	// Step identifies the step that is beginning.
	Step Step

	// Output reads everything the step produces: xcaddy's own log
	// lines (in their usual "[INFO] ..." format) as well as the
	// stdout and stderr of the underlying go commands (go mod,
	// go get, go build, ...). The Builder buffers this output
	// internally, so the build never blocks on a slow reader
	// mid-step; output is available to read the moment it is
	// produced.
	//
	// When the step ends, the Builder closes the write side, so
	// after any remaining buffered output is drained the reader
	// reaches EOF; reading until EOF is the only lifecycle a
	// consumer needs. Output that is never read is discarded when
	// the step's buffer is released.
	Output io.Reader
}

StepEvent describes a build step that is about to begin. It is passed to a Builder's OnStep callback.

Directories

Path Synopsis
cmd
xcaddy command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL