I’ve been in the web game for quite some time and have my fair share of web server software. I’ve used Microsoft’s Internet Information Services (IIS), Apache httpd, as well as NGINX, and while they all thrive in their own ways, they’ve been overkill for most of my use cases. This is where Caddy comes in, a lightweight alternative to these seasoned, but often heavy web servers.
We’re going to see how to use Caddy and learn why it is so powerful while using minimal effort on a developer operations side.
If you’ve been keeping up with the blog, you’ll remember that this isn’t the first time I talked about Caddy. I actually interviewed its creator, Matt Holt, on a podcast episode titled, The Go Programming Language and Modern Development. If you haven’t listened to the episode, I encourage you to check it out. However, from the title alone you can probably determine that Caddy was written using the Go programming language. Given the nature of Go, this means that a Caddy binary was cross-compiled to work on virtually every platform out there, including Mac, Windows, Linux and even Internet of Things (IoT). The cross-platform functionality is great, but what’s awesome is that it is a self contained application, meaning you have a Caddy binary and a site configuration file.
From a features perspective, Caddy offers the following:
Of course those are not all the features, but they are the few that I found value in when having experienced them with other servers.
Let’s dig deeper into the easy configuration aspect of Caddy.
Each application that you serve will use what is known as a Caddyfile and it contains, in most scenarios, just a few lines of configuration data rather than the hundreds that you might find with NGINX or Apache httpd. Take a look at the example below:
*:8080 {
root /path/to/html
}
The above example is probably the most basic Caddyfile file you can have. It is saying to listen on port 8080 and serve content at the listed path.
Alright, let’s add a little complexity to the configuration file:
*:8080 {
root /path/to/html
errors {
404 404.html
}
gzip
expires {
match .html$ 1d
match .xml$ 1d
match .json$ 1d
match .js$ 1w
match .css$ 1w
match .png$ 1w
match .jpg$ 1w
match .gif$ 1w
match .svg$ 1w
match .ogg$ 1m
match .ttf$ 1m
match .otf$ 1m
match .woff$ 1m
match .eot$ 1m
}
}
We’ve changed the configuration a bit. This time we are compressing the server responses, defining a landing page for any 404 errors, and defining cache expiration times for various file extensions.
A full list of the configurations that you can add to your Caddyfile file can be found in the official documentation.
So we have our Caddyfile file created, now we might want to use it to serve our application either directly on the host or within a Docker container.
The easiest thing to do would be to run Caddy from the Terminal:
caddy
The above command assumes that you’re in the same directory as your Caddyfile file. If you’re not, then it will use the bare minimum to serve your application, kind of like how Python does it. You could also execute the following:
caddy -conf /path/to/Caddyfile
Not difficult as of now, correct?
Let’s take a look at the Docker approach to deploying a web application with Caddy. Assuming that you’ve got Docker installed and configured, create a Dockerfile with the following:
FROM alpine:latest
RUN apk add --no-cache openssh-client tar curl
RUN curl --silent -o - "https://caddyserver.com/download/linux/amd64?license=personal" | tar --no-same-owner -C /usr/bin/ -xz caddy
RUN chmod 0755 /usr/bin/caddy
EXPOSE 80 443
WORKDIR /srv
COPY . /srv/
ENTRYPOINT /usr/bin/caddy
The above blueprint will use the very sleek and very slim Alpine Linux. It will install the necessary dependencies and obtain Caddy via a cURL command. The same command will drop Caddy into a more appropriate directory and then give it the correct permissions.
Assuming the Dockerfile file is in the same directory as our Caddyfile file and our web content to be served, it will all be copied into a /srv directory in the Docker image. When we deploy the image as a container, Caddy will run, using the transferred Caddyfile configuration file.
We can build a Docker image with the following command:
docker build -t caddy-project .
The above command will create an image called caddy-project using the contents of the current working path. To deploy our image as a container we could do the following:
docker run -d -p 80:8080 --name caddyproject caddy-project
The above command will port map 80 on the host to 8080 in the container. This assumes that 8080 is the port in the Caddyfile file.
Still not so bad, correct?
While I didn’t go into intense amounts of detail on what you can do with Caddy, take my word on it that Caddy is a powerful tool and a great alternative to some of the other software out there such as NGINX and Apache httpd. You can do a lot with a little when it comes to Caddy and that is why I like it so much. Gone are the days where you have to be an expert with system administration and developer operations. Instead you can take an easy to understand configuration file and deploy your application with minimal effort.