Building a Caddy container stack for easy HTTPS with Docker and Ghost

I'm building a Caddy-stack to serve up HTTPS sessions, with a Ghost back end, using Docker containers, orchestrated with Docker Compose. You'll easily be able to replace the Ghost back end with other container-based apps such as Nginx or Wordpress.

Building a Caddy container stack for easy HTTPS with Docker and Ghost

I'm building a Caddy-stack to serve up HTTPS sessions, with a Ghost back end, using Docker containers, orchestrated with Docker Compose. You'll easily be able to replace the Ghost back end with other container-based apps such as Nginx or Wordpress.

As a longtime user of Traefik, I was pleasantly surprised when I tripped over Caddy. It's another proxy application like Traefik that ticks all the boxes. Reverse proxy, routing, https, open source, container based, easy to use. And that last one is crucial.

While Traefik is very cool and a great community, I had been beating my head against the wall trying to get a really basic running model of their new version 2 up and running. The config and syntax complexities were dragging me down, and there weren't really many resources or HTTPS examples online. The tech road was meandering in a wildly zig zag way.

In fact, it was while digging around for even a simple example of Traefik V2 with Docker Compose + HTTPS that I found a blogger - also struggling to get it going - who wrote the fateful words, "discovered caddy, seems simpler". And here we are. A cloud test server and less than an hour later, it was up and running.

Incidentally, this is my first outing making use of the excellent for my diagram, with a cool sketch library from Rough-JS.

Prerequisites for the build

I am using a $5 AWS Lightsail server in the Oregon region. Most VPS's (Virtual Private Server) will do, I have my farm in the same place, I find it a fair deal for the included bandwidth and like having the vast AWS infrastructure nearby should I want to make use of it. Variations on this environment might work but this is what works here.

  • An AWS Lightsail $5 VPS with 1GB memory
  • Ubuntu 18.04
  • A running docker compose environment, built as per this great DigitalOcean article
  • A domain name or subdomain name you can point to the VPS public IP address
  • Optional - an IP whitelist on your cloud server so only you can access the test application

Getting a base Caddy set up

I normally keep all my docker stuff under custom directory /data, but it's up to you. Wherever you see /data/caddy, replace with your own core directory. There are a few volumes at play.

  • /data/caddy : the Caddy and Compose root
  • /data/caddy/Caddyfile : the centre of the universe
  • /data/caddy/data : the house for certificates (optional)
  • /data/caddy/config : JSON config files (optional)

Create some directories

$ mkdir -p /data/caddy/data
$ mkdir -p /data/caddy/config
$ cd /data/caddy

Create a Compose file

I also typically keep my Docker Compose control file in the same place as the "head vampire" container, in this case Caddy. It can be anywhere that suits you. With your favourite text editor, create & open docker-compose.yml and seed with this.

version: "3"

                external: true

                image: caddy:2-alpine
                restart: unless-stopped
                        - "80:80"
                        - "443:443"
                        - /data/caddy/Caddyfile:/etc/caddy/Caddyfile
                        - /data/caddy/data:/data
                        - /data/caddy/config:/config
                        - web

Caddy will function with only the Caddyfile mapped, and is fine for testing, but having the data and config volumes allows externalising of some key files, for backup or consistency when upgrading the core container image.

Create a Caddyfile

A Caddyfile is structured to have blocks for global settings, snippets, and server. The overview is on the Caddy site here. I am using the following.

    # Global options block. Entirely optional, https is on by default
    # Optional email key for lets encrypt
    # Optional staging lets encrypt for testing. Comment out for production.
    # acme_ca
} {
    reverse_proxy ghost:2368
} {

The optional email field here is linked to the LE (Let's Encrypt) certificates Caddy will issue automatically. If you are going to be hacking around with this I recommend using the LE staging server, just remember to comment the acme_ca entry out before going live.

Create a Docker network

If you haven't already, create the "web" network: docker network create web

You can check for it with: docker network ls

Point your domain

Assign your domain to the IP address of your VPS. Ideally use a static IP on your instance. In this case it could be or *

Doing a lookup for should resolve to the VPS IP address.

Start the Caddy container

Running a docker-compose up -d should bring up the caddy container. If you do a docker ps you should see something like this:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                NAMES
fc43f933e554        caddy:2-alpine      "caddy run --config …"   24 hours ago        Up 14 seconds>80/tcp,>443/tcp, 2019/tcp   caddy_caddy_1

If all looks good, you can proceed to adding an application container.

Add Ghost to the docker compose file

This doesn't have to be Ghost. You could easily add a different container that serves up data, such as Nginx. Be aware though, we have added the Ghost port in the Caddyfile. Remember the entry ghost:2368 - this would need to be replaced with the port for your chosen service and suitable aliases.

Edit the docker compose file with your favourite editor and add this.

                image: ghost:3.22-alpine
                restart: unless-stopped
                        - url= # Change to your domain
                        - /data/myghostapp:/var/lib/ghost/content
                        - web
  • Create the Ghost application volume for mounting with mkdir /data/myghostapp
  • Change the domain in the URL parameter to be the domain or subdomain you are pointing to this server. This should match the domain in the Caddyfile.
  • If you are looking to build this site to keep, check Docker hub for an up to date Ghost image

Start the Ghost container

Run docker-compose up -d and all going well you should see the images update, and the containers start.

Shortly, entering your domain into a browser such as should take you to the vanilla Ghost pages, ready to go. You can also test the www redirect we added to the Caddyfile, if you enter your equivalent of into the address bar, it should redirect to the main URL without the www.

If you were using the Let's Encrypt staging server, and you want to keep the stack running, comment it out from the Caddyfile at this point. You may need to scrub under the /data/caddy/data/caddy directory if sessions are getting corrupted.

Performance test for basic page loads

Server-side, Caddy is using about 18MB memory and Ghost around 94MB.
Making use of on the Ghost welcome page (includes calls out to Ghost for static files), I get document complete in 2.76s with a payload of 1184KB.

Finishing up

We wouldn't be at the end of the Tech Road without some thought for potential production use. If you are going ahead with a more permanent build of this, be sure to check out my articles on logging, and backups.

Check out the Caddy header definition, for production you will want to incorporate some security options. I will cover this in a future post but in the meantime see the example security headers here.

On the off chance you set up automated container image updates as per my post, or have any script that runs against an old compose file location (I was previously stashing mine under /data/traefik), you will need to alter your update scripts to reflect your new compose file location.

We have scratched the surface: There is a growing set of Caddy functionality available, including regular expressions, rewrites, redirects, variable substitutions, load balancing and direct web server, described on the Caddy web site. I'm going to make it my main web broker from this point forward, and will next look at converting this very site to start using it.

Main golf caddy photo courtesy of Kenan Kitchen on Unsplash.

You are welcome to comment anonymously, but bear in mind you won't get notified of any replies! Registration details (which are tiny) are stored on my private EC2 server and never shared. You can also use github creds.