Caddy is an excellent HTTP/2 web server with automatic HTTPS. The configuration is simple and approachable, providing secure defaults and self-contained binaries that run on every platform.
I have found Caddy to be my go-to server for running PHP Docker containers in production. Using the official PHP-FPM image, we can run Caddy and start PHP-FPM in the background within the same container.
I find that running PHP-FPM and Caddy together simplifies my stack by eliminating the need for FastCGI network communication between something like Nginx and PHP-FPM.
Setting up a Project
Using Laravel, here’s how I like to organize my Docker setup:
$ cd ~/Code/ $ laravel new docker-caddy $ mkdir docker/ $ touch docker/Caddyfile $ touch docker/Dockerfile $ touch docker-compose.yml
I like to create a
docker/ folder to contain my Docker-specific code, and sometimes I even break it out into subfolders like
docker/php/ depending on the needs of my project. In this case a
docker-compose.yml file to orchestrate containers in development, a
Dockerfile to build the image, and a
Caddyfile to configure the web server.
The Caddyfile is a text file that configures how Caddy runs. The syntax is clean and simple, often requiring a one-liner to enable features with practical conventions.
Here’s a typical Caddyfile for a Laravel project:
In production I usually terminate SSL in the cloud provider I am using, and Caddy runs on either its default port of
80. In the Caddyfile above, this server will respond to
0.0.0.0:2015 because no port was specified.
gzip directive is a simple one-liner for enabling gzip with reasonable defaults. You can get more granular control if needed.
We use the
fastcgi directive to proxy to respond on the
/ request path. The
php on the end of the directive defines some presets for the directive:
ext .php split .php index index.php
rewrite directive is rewriting all URLs to point to an
index.php file, which is our
public/index.php file for Laravel.
The last chunk of code sends all logs to
stdout which is what we want in Docker, and the server logs will show up in the console.
on startup event starts PHP-FPM in the background so that Caddy can communicate with PHP.
If you haven’t used Caddy, an excellent place to start is the Caddyfile tutorial which does an excellent job walking you through how to configure Caddy.
Here is the full Dockerfile located at
docker/Dockerfile in the project:
The Dockerfile is extended from the official PHP-FPM image, and then we install the Caddy binary from the official build server with the personal license. We make the
caddy bin executable and install a few PHP extensions.
Next, we copy the source code and Caddyfile and make sure that the
www-data user owns the application files in
The last line provides defaults for an executing container, which will be running caddy with the
/etc/Caddyfile configuration, and logging output to
Note that we are installing a few plugins
http.expires,http.realip, and using
license=personal. If you are using Caddy for a commercial project, you will need a license or build Caddy from source yourself.
Try it Out
Let’s run our Caddy and PHP-FPM image using Docker Compose! Here’s what a simple
docker-compose.yml file would look like:
We can build the image and run it with one command:
$ docker-compose up --build
Now if you visit http://localhost:8080 you will see the Laravel welcome page and Caddy logs in the console!
I like using Caddy because we can combine running PHP-FPM and the web server in the same container. Combining both in one container removes the need for networking between the web server and PHP-FPM. There’s nothing wrong with either approach, but I prefer less moving parts.
Caddy is open-source, but if you want to use the build server like we’re doing in the
Dockerfile for a commercial purpose, you will need an official license. You can also build Caddy yourself, but the plugin build system is full of useful plugins and will save you a ton of time.