Published: Monday, May 9, 2016 Last modified: Monday, Dec 9, 2024

Caddy running in Docker on Archlinux

Instead of running Caddy via go get, I’ve opted on my local machine to use Docker. Why? Because that’s how my servers run, so I thought it would be sensible to reproduce the environment locally on my laptop.

What is Docker?

I think of Docker as simply a Linux container front end. Like a package manager like npm say. However instead of searching for dependencies, you usually have the entire self-contained product in a Docker container.

Assuming you have Docker setup, to run a “packaged” Caddy in a container, it can be as simple as running:

docker run --rm -p 2015:2015 abiosoft/caddy:php

So why is this cool?

It’s cool because we don’t have to fiddle with a golang environment or which binaries we should download and how to setup PHP. It just works!

I don’t need to actually know too much about the boring internal stuff. I supply a config file, some directory and port mappings … and BOOM … I’m away!

Finally & most importantly to me, someone else maintains it & abiosoft is doing a good job!

So how do I integrate this to my system?

Unsurprisingly linux containers best run on Linux & I recommend using a Linux system with systemd with Docker. Most “modern” linux systems do this now. This is what my /etc/systemd/system/caddy.service looks like:

[Unit]
Description=Caddy
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
User=hendry
ExecStartPre=-/usr/bin/docker kill caddy
ExecStartPre=-/usr/bin/docker rm caddy
ExecStartPre=-/usr/bin/docker pull abiosoft/caddy:php
ExecStart=/usr/bin/docker run --name caddy -v /home/hendry/caddy/Caddyfile:/etc/Caddyfile -v /srv/www:/srv/ -v /home/hendry/caddy/caddy-certs:/root/.caddy -p 80:80 -p 443:443 abiosoft/caddy:php
RestartSec=5
Restart=always

[Install]
WantedBy=multi-user.target

You might be wondering, why are there these pre steps to {kill,rm,pull} Caddy? It looks really ugly but what we are doing here is checking for updates and using that update. No more patching! A restart is almost all we need to ensure we are current.

Notice I bind my configuration at /home/hendry/caddy/Caddyfile to the location the container expects that to be in. These little mappings should be documented in the container’s README or in the Dockerfile.

My configuration /home/hendry/caddy/Caddyfile looks like:

natalian:80 {
	tls off
	root natalian
	header / Content-Encoding "gzip"
	log stdout
	errors stdout
}

config:80 {
	tls off
	root config
	startup php-fpm
	fastcgi / 127.0.0.1:9000 php
	log stdout
	errors stdout
}

Since I run Caddy locally on my laptop for development, I don’t need Caddy’s awesome automatic HTTPS feature.

Notice I use stdout, so I can use journalctl -u caddy -f to view & most importantly maintain the logs.

Hostnames

I manually add the names of my hosts into /etc/hosts. 172.17.0.1 in my case is the IP of the Docker instance. If anyone know how to make this easier, please let me know.

172.17.0.1 natalian config

So typically once I edit ~/caddy/Caddyfile with a new site, I then edit /etc/hosts and then sudo systemctl restart caddy.

Tip: To have it startup on boot: sudo systemctl enable caddy.

Conclusion

This is my current local laptop configuration, but the production version on CoreOS on Digital Ocean is almost the same except the hostnames and the user being core instead of hendry.