Setting up Ghost using Docker and Caddy

After having heard about on it the interwebs for a bit, I finally decided to play around with Caddy myself last summer and see what the fuss was all about.

Reader, I dig it!

I mean, don't get me wrong – let's encrypt is AMAZING and it's no exaggeration that it single-handedly pulled forward HTTPS / TLS proliferation by many years (see their 2023 report). The internet today is more secure thanks to it.

Buuut. There are many different web servers and proxies, many different platforms & operating systems to support, and Python packaging / tooling / performance is still not great. So making it work, let alone work well, is no easy task. Which is to say, my experience using letsencrypt (via certbot) in a container environment (specifically Kubernetes / GKE) in concert with ingress-nginx was mediocre, at best. Things would break in weird ways, and when they did, debugging what was wrong took forever.

So the idea of a web server that also handles SSL certificates was very compelling. And it can act as a reverse proxy. And it has a robust plugin system. Very compelling indeed!

Naturally, the first thing I decided to host with Caddy was this blog!

Step 1: Spin up Ghost in a Docker container

This part is pretty straight-forward. Here's my Docker compose file:

    image: ghost:latest
    restart: always
      # 2368 is the default port Ghost will use in the container
      # We'll map this to port 8001 on the local host
      - 8001:2368
      - diwaker-io-data:/var/lib/ghost/content
      # see
      database__client: mysql
      database__connection__host: IP_ADDRESS
      database__connection__user: DB_USER
      database__connection__password: DB_PASS
      database__connection__database: DB_NAME
      mail__transport: SMTP
      mail__options__auth__user: MAILGUN_USER
      mail__options__auth__pass: MAILGUN_PASS
      mail__options__port: 2525


Step 2: Configure Caddy as a reverse proxy

Next, we'll tell Caddy to reverse proxy all traffic to to port 8001 (which will then be served by the Ghost instance in the Docker container we just setup). Caddy is commonly configured via Caddyfiles:

The Caddyfile is a convenient Caddy configuration format for humans. It is most people's favorite way to use Caddy because it is easy to write, easy to understand, and expressive enough for most use cases.

Here's our Caddyfile: {
        reverse_proxy localhost:8001

That's it! Note that nowhere did we specify anything about HTTPS, TLS, configure certificates or certificate authorities – nothing.

Now you just run Caddy, with this Caddyfile and you're all set! Depending on your platform and how you installed Caddy, something like this should work: /path/to/caddy run --environ --config /etc/caddy/Caddyfile

ps: Caddy logs this when exiting. Cute.

{"level":"warn","ts":1705024830.2319794,"msg":"exiting; byeee!! 👋","signal":"SIGTERM"}