Deploying a Caddy Web Server with Docker – CloudSavvy IT

Image with the Caddy web server project logo

Caddy is a popular modern web server designed for high performance and memory security. It is written in Go, works without dependencies, has built-in support for static site rendering with Markdown, and offers automatic HTTPS.

Caddy is focused on providing a simple server management experience that gives you useful functionality as standard. It can be easier to configure and maintain than competing systems like Apache and NGINX. In this article, we’ll show you how to run your own server with minimal installation by using Docker with the official Caddy image.

Select an image tag

The Caddy statue comes in a few different flavors. The latest version of Caddy is shared by everyone and at the time of writing it is v2.4. You can use 2.4.x (where x is replaced by a specific patch version), 2.4 or 2 to pin to the main, secondary or patch component.

Caddy works with Linux and Windows Docker hosts. Alpine Linux, Windows Server Core 1809, and the Windows Server Core 2016 LTSC release are the current operating system options. Referring to a bare Caddy tag like caddy:2 will select the correct image for your platform; you can use variations like 2.4-alpine or 2.4.6-windowsservercore-1809 instead to be more explicit.

Start a basic server

Caddy comes in a ready-to-use configuration. The Docker image will serve your web content from the /usr/share/caddy folder. You can add your files to the container by associating a host directory with this path.

Caddy also has separate configuration and data folders to which you must mount Docker volumes. The /config folder is optional, but recommended; it stores your configuration files, but since they are converted to API requests, they don’t have to be kept strictly. The /data location is vital as it contains Caddy generated TLS certificates, private keys, and the final server configuration processed by the API.

docker run -d -p 80:80 \ -v ./my-website:/usr/share/caddy/ \ -v caddy-config:/config -v caddy_data:/data caddy:2

As an HTTP server, Caddy listens on port 80 by default. This is bound to port 80 on your host via the -p flag in the example above.

Now you can visit http://localhost in your browser to access your site. You should see the index.html from your linked content folder. You’ll get the default Caddy landing page if you haven’t tied anything to /usr/share/caddy.

Set up HTTPS

One of the main features of Caddy is the automatic TLS support, so we would be remiss not to use it. In addition to port 80, you must also bind port 443 so that Caddy can receive HTTPS traffic. The only other change is to specify the domain name on which your site will appear. Everything else is handled by Caddy.

docker run -d -p 80:80 -p 443:443 \ -v ./my-website:/usr/share/caddy/ \ -v caddy-config:/config -v caddy_data:/data caddy:2 caddy- file -server –domain

The command after the image name is passed by docker-run to the docker image entry point. In this case, that means caddy file-server … is running as the container’s foreground process. The –domain flag is used to set the domain for which Caddy will obtain an HTTPS certificate. You should verify that you have an A-type DNS record pointing to your Docker host’s IP address before starting the container.

Add your own caddy file

Caddy normally uses a configuration file called Caddyfile to define routes and change server settings. The Docker image loads the Caddyfile in /etc/caddy/Caddyfile. Attach your own file to this path to override the defaults for /usr/share/caddy:

docker run -d -p 80:80 -p 443:443 \ -v ./Caddyfile:/etc/caddy/Caddyfile -v caddy_data:/data caddy:2

Here’s a simple Caddy file for a site called with HTTPS enabled:

{ email } { reply “It works!” †

This minimal configuration sets the global email address to This email address will be used when requesting Let’s Encrypt certificate. The block provides routing rules for handling requests to your domain by Caddy. In this case, the server will always respond with a static message.

More information about the Caddyfile is available in the Caddy documentation. Docker doesn’t change anything here: as long as your file is available in /etc/caddy/Caddyfile, Caddy will load and use it.

Create Docker images for your sites

So far, we’ve looked at using ad-hoc Caddys by launching containers directly from the base Caddy image. In practice, you’re more likely to want to create special images for your sites so that you don’t have to link your content every time you launch a container.

The Caddy base image is ready to expand with your own instructions for adding content and configuration. Here’s an example of a Docker file that contains a Caddy file and copies the contents of your site to a custom folder:

FROM Caddy:2.4 WORKDIR /my-site COPY Caddyfile /etc/caddy/Caddyfile COPY *.html ./ COPY *.css css/ COPY *.js js/

Now you can build and run your image to start a Caddy server preconfigured for your site:

docker build -t my-site: last . docker run -p 80:80 -p 443:443 -v caddy_data:/data my-site:last

Many sites will want you to add additional Caddy modules for added functionality. The best way to work these into your Dockerfile is through Caddy’s special builder image. This includes tooling needed to build a custom Caddy instance with specified modules installed.

Docker’s multi-stage builds are ideal for this workflow. Here’s an example adding Caddy’s Reply Replacing module so you can rewrite parts of reply data using rules in a Caddy file:

FROM Caddy:2.4-builder AS caddy-build RUN xcaddy build –with FROM Caddy:2.4 COPY –from=caddy-build /usr/bin/caddy /usr/bin/caddy WORKDIR /my-site COPY Caddyfile /etc/caddy/Caddyfile COPY *.html ./ COPY *.css css/ COPY *.js js/

The first build phase produces a Caddy binary with the response replacement module baked in. The xcaddy command available in the builder image places the output in /usr/bin/caddy. The second stage uses the default Caddy base image, but overwrites the provided binary with the custom one. Your content will then be layered as normal; the result is a Caddy server that contains additional modules while retaining full support for the rest of the features in the Docker base image.


Caddy is a modern web server that is an excellent choice for efficiently serving static files. It offers a compelling feature set with top-notch HTTPS support, built-in template rendering, and Markdown integration.

By using Docker to host your Caddy server, you can quickly deploy an instance without manually downloading binaries or installing service files. It’s a good way to try out Caddy or run alongside existing workloads in a clustered environment.

Since Caddy can act as a reverse proxy and load balancer, you could use it as an entry point to route traffic to your other Docker containers. The popular Caddy Docker Proxy module extends the server’s built-in capabilities with Traefik-like support for automatic route discovery via Docker container labels.

Caddy takes an API-first approach to configuration that simplifies the management of instances running in a container. You don’t have to worry too much about injecting configuration files or managing volumes. As long as the /data directory is preserved, you can make API requests to change Caddy’s behavior without using the Docker CLI. This can make it a better choice for containerization compared to more traditional options like Apache and NGINX.

This post Deploying a Caddy Web Server with Docker – CloudSavvy IT

was original published at “”