How to use Caddy to get HTTP/2 support in Rancher

After having used Tutum and Docker Cloud for my hobby projects I've now switched to Rancher. It's basically the same thing as Docker Cloud, but it's self hosted and gives you better control (and a lower price tag). For those that don't know, Rancher is a Docker orchestration tool handling deployment of containers, health checks, inter-container networking etc.

With Rancher in place I started looking into HTTP/2. The new version of HTTP has a lot of benefits over the previous one (HTTP/1.1). Most prominent:

  • It's binary instead of text based which makes it more performant
  • It supports multiple prioritized streams within one connection, so no more need for a lot of separate connections for resources needed by a page, blocking, hacks like sprites etc.
  • Header compression (headers mean a lot of overhead in HTTP/1.1)
  • Better SSL (old insecure versions of TLS are no longer supported).

Overall HTTP/2 should increase performance in any site without any changes to the site itself. If you always use SSL (recommended but not required) you will also get a secure site. In search results, Google promotes sites with SSL and high performance over unencrypted slower sites - so it's a good thing for SEO as well.

So how do I get HTTP/2 in Rancher?

Enter Caddy. Caddy is a server proxy that handles incoming requests and distributes them to internal servers based on ports, host names and IPs - much like nginx and haproxy - including load balancing. One big difference though, is that Caddy has built-in support for Let's Encrypt, the service that issues SSL certificates for free.

The easiest way to get a Caddy container deployed in rancher is starting from a good base image. I recommend zzrot/alpine. It's based on Alpine Linux so it's really small, and the only two files you need is a customized Caddyfile with your site configurations and the Dockerfile.

Example Caddyfile for a Ghost blog:

https://www.osirisguitar.com {
  tls anders@bornholm.se
  proxy / heavy-metal-coder-ghost:2368 {
    transparent
  }
  log stdout
}

Explanation:

  1. Line one identifies the host name, and by specifying https:// Caddy will still listen to HTTP on port 80 but redirect all requests to HTTPS.
  2. Line two tells Caddy to use SSL and to automatically aquire an SSL certificate from Let's Encrypt.
  3. Line 3 specifies where the requests should go. heavy-metal-coder-ghost is the name of the link from my Caddy service in Rancher to the Ghost service in my Heavy Metal Coder stack in Rancher, which exposes port 2368 (no need to use ports in the service, that will just risk collision on the Docker host).
  4. The transparent option for the proxy sets common headers for a proxy allowing the underlying service to know stuff about the original request. If you need to customize how the proxying is done, there are loads of more options in the Caddyfile.

For a full example, check out my Caddy repo: https://github.com/osirisguitar/infra-caddy

Create your own Caddy file, build and push to the Docker repository of your choice. With Docker Hub it would be something like:

$ docker build -t osirisguitar/caddy . && docker push osirisguitar/caddy

Creating a stack in Rancher

On a Docker host only one container can reply to a port, so I usually place my load balancer in a separate stack that servers requests to all the other stacks. This is a docker-compose.yml for a Caddy stack serving this blog:

infra:
  ports:
  - 443:443/tcp
  - 80:80/tcp
 external_links:
  - heavy-metal-coder/ghost:heavy-metal-coder-ghost
 labels:
   io.rancher.container.pull_image: always
 image: osirisguitar/caddy:latest
 volumes:
 - /data/caddy:/root/.caddy

The volumes directive is really important. It makes Caddy store the challenges for Let's Encrypt in a the directory /data/caddy on the Docker host. This is important since without it, Caddy would request a new certificate from Let's Encrypt on every redeploy and would hit Let's Encrypt's request limit of five certificates per week pretty quickly (setting up a stack usually involves a couple of retries before everything's working, at least for me). If you have multiple hosts, the best solution is to sync this directory between hosts (with rsync or something similar).

Replace the image with your own image, created above. If you did everything right, after deployment your site will now use HTTP/2.

Note: You have to set Caddy up to listen to both 80 and 443. With only 443, there is no way for Let's Encrypt to make the call to your Caddy to make the challenge/response required to verify that you actually control the domain you are requesting the certificate for. Let's Encrypt doesn't have a UI or user accounts, so a working challenge response is required for the certificate request to work.

Good luck!

comments powered by Disqus
Find me on Mastodon