What do we want?

At the end - to create very simple docker stack containing caddy serving simple responses over tls using provided keys.

What do we need?

  1. Working docker installation, including knowing the path to docker volumes (may come handy for copying files, ie).

    One of the possible paths is: \\wsl.localhost\docker-desktop-data\version-pack-data\community\docker\volumes

  2. Valid wildcard ssl key

    Probably it will come stored as .pfx file, caddy needs cert/key pair so we will need openssl installed and ready to be used.

    To extract key/cert from .pfx file, several openssl commands should be executed:

    • Extract private key from .pfx to .pem file:

      openssl pkcs12 -in <cert-name>.pfx -nodes -nocerts -out server.pem
      

      You should be asked for password, and private key server.pem should be created

    • Convert .pem format to rsa format:

      openssl rsa -in .\server.pem -out .\server.key
      

      Now we should have server.key

    • Extract server certificate from .pfx:

      openssl pkcs12 -in .\2022wild.domain.com.pfx -nokeys -chain -out cert.pem
      

      Just a password and a nuance away, and we are done: the two files we should keep are server.key and cert.pem. Rest (.pfx and .pem) should be removed.

    If it is, by any chance, already created as cert/key pair skip those troublesome sections above.

  3. Valid DNS configuration

    Beyond scope of this, but for simple use case just add those lines to local hosts file, depending on the available wildcard certificate

How shall we do it?

Create some folder and in that folder create empty file named compose.yml and paste this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
volumes:

  reco-caddy-data:
    external: true
  reco-caddy-config:
    external: true

services:

  caddy:
    image: caddy:2.6
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - reco-caddy-data:/data
      - reco-caddy-config:/config

What are we doing here? Creating one service (caddy) exposing some default ports and set up two volumes that caddy will use for persistence (data and configuration).

Now we can boldly try one docker compose up -d and get response that pretty much looks like this:

[+] Running 5/5
 ✔ caddy 4 layers [⣿⣿⣿⣿]   0B/0B   Pulled    4.3s
   ✔ 91d30c5bc195 Pull complete    0.8s
   ✔ 7f137c1fd65a Pull complete    0.9s
   ✔ 123731571dfc Pull complete    0.9s
   ✔ 9ab4cbb8b7b7 Pull complete    2.0s
[+] Running 1/0
 ✔ Network step1-caddy_default  Created    0.1s
external volume "reco-caddy-data" not found

Additional external in volume declarations means that docker will treat those volumes as externally created and will never try to delete them. However, those kind of volumes must be created manually, so let’s do that:

docker volume create --label reco-caddy-data reco-caddy-data
docker volume create --label reco-caddy-config reco-caddy-config

Another docker compose up -d should succeed with message ✔ Container step1-caddy-caddy-1 Started

Do another http://localhost/ in the browser and you should see default caddy page. Congratulations.

At some point in time we will have to configure caddy somehow, so let’s put some basic files to do that. Create file named Caddyfile in same folder as the rest of the files, and paste following into it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    auto_https disable_certs
}

*.domain.com {
    tls /config/cert.pem /config/server.key
    handle {
         respond "Hello world!"
    }
}

One thing to note here is that exact content of the file depends on your wildcard certificate. In example above, certificate is valid for *.domain.com, so update the value according to your needs.

What are we configuring caddy to do? First - to disable authomatic tls certificate enrollment and to leave everything else, including forwarding to https endpoint.

Next, for urls that fall under *.domain.com, we configure tls layer to use our key/cert pair: tls /config/cert.pem /config/server.key part is telling caddy to use files /config/cert.pem and /config/server.key as cert and key, respectfully.

So… files should be in /config directory somewhere… Remember those annoying volumes from compose.yml?

    volumes:
      - reco-caddy-data:/data
      - reco-caddy-config:/config

Here it states that volume reco-caddy-config should be mounted as /config folder as caddy docker container is concerned. Therefore we should copy our cert/key file pair to wherever our docker volume reco-caddy-config really is.

One example may be \\wsl.localhost\docker-desktop-data\version-pack-data\community\docker\volumes\reco-caddy-config\_data

After you have copied cert and key files, update compose.yml file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
volumes:

  reco-caddy-data:
    external: true
  reco-caddy-config:
    external: true

services:

  caddy:
    image: caddy:2.6
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - reco-caddy-data:/data
      - reco-caddy-config:/config

and do docker compose up -d to recreate containers (as compose.yml has changed).

If everything goes without problems two things should be noted:

One - if you refresh your browser that, hopefully, still shows default caddy page - it will redirect to https://localhost and it should not work with some kind of ‘connection for this site is not secure’ message.

Two - if you try something.domain.com (or whatever your domain may be) (and if dns has been set somehow) you should be greeted with one warm “Hello world” response. Browser should not display any security related warnings, and if it is really the case - pat yourself on the back, you have just configured basic structure that we will continue to build on in next installments.

Two files that you should carry to the next lesson are Caddyfile and compose.yml.