haproxy-letsencrypt-docker.md
... ...
@@ -2,13 +2,14 @@
2 2
<!-- SUBTITLE: This is 3 lines of bash when docker's not involved -->
3 3
4 4
# What are we doing here?
5
-Let's set up [HAProxy](https://www.haproxy.org/) with some lovely free certs from [Let's Encrypt](https://letsencrypt.org/) via [certbot](https://certbot.eff.org/) for a couple of domains.
5
+Let's set up [HAProxy](https://www.haproxy.org/) with some lovely free certs from [Let's Encrypt](https://letsencrypt.org/) via [certbot](https://certbot.eff.org/) for a couple of domains, each domain fronted by a different container.
6 6
Everything running in [docker](https://www.docker.com), and all tied together with [docker-compose](https://docs.docker.com/compose/).
7
-We'll use docker [user-defined networks](https://docs.docker.com/v17.09/engine/userguide/networking/#user-defined-networks), because that's the Right Thing To Do.
7
+No k8s, no swarm, just one woman/man/other and one host/VM/other. Old-ish skool.
8
+We'll use [docker user-defined networks](https://docs.docker.com/v17.09/engine/userguide/networking/#user-defined-networks), because that's the Right Thing To Do here.
8 9
9
-I'll use 'domain1.example.com' and 'domain2.example.com' as the domains involved. The domain1 site is served from a container called 'container1', and domain2 from a container called 'domain2' You might have more. Or less. Edit as appropriate.
10
+I'll use 'domain1.example.com' and 'domain2.example.com' as the domains involved. The domain1 site is served from a container called 'container1', and domain2 from 'container2' You might have more. Or less. Edit as appropriate.
10 11
11
-This wiki is set up very similarly to the below - the running config is on [my github](https://github.com/ilikejam/home-network).
12
+This wiki you're reading is set up very similarly to the below - the running config is on [my github](https://github.com/ilikejam/home-network).
12 13
13 14
# This should be easy. Right?
14 15
Docker: easy.
... ...
@@ -18,21 +19,24 @@ Let's Encrypt: easy.
18 19
Docker and HAProxy and Let's Encrypt: pain in the arse.
19 20
20 21
There's a few things that make this a bit of a hassle:
21
-1. We want haproxy to be running on port 80/443, but those are the ports certbot needs to do validation<br/>We'll have to do this in two stages.
22
-2. haproxy with the default config won't start up if it can't resolve the container IPs for the backends.<br/>Since certbot is just a command to be run in a container, it probably won't be running when haproxy starts up.<br/>Some extra config is needed in haproxy.
23
-3. certbot needs to be run once in one way to request the certs, and then every couple of days/weeks in another way to check and renew certs.<br/>We'll need two different incantations for certbot.
24
-4. When the certs are renewed, we'll need to tell haproxy to pick them up<br/>Some docker-in-docker magic is required.
25
-5. certbot doesn't know how to make haproxy-complicit cert pem files<br/>We'll need to do a little scripting.
22
+* We want haproxy to be running on port 80/443, but those are the ports certbot needs to do validation.<br/>We'll do this in two stages for minimum pain.
23
+* haproxy with the default config won't start up if it can't resolve the container IPs for the backends.<br/>This is a general problem with haproxy and containers. We'll do some config to make it work.
24
+* certbot needs to be run once in one way to request the certs, and then every couple of days/weeks in another way to check and renew certs.<br/>We'll need two different incantations for certbot.
25
+* When the certs are renewed, we'll need to tell haproxy to pick them up<br/>Some docker-in-docker magic is required.
26
+* certbot doesn't know how to make haproxy-complicit cert pem files<br/>We'll need to do a little scripting. Not much though. 3-lines, max.
27
+
28
+Let's do this thing.
26 29
27 30
# Stage 0 - setup
28 31
This all assumes that your soon-to-be certificated domains' A records are all pointing at the docker host (or port-forwarding router, or whatever), and you can reach the docker host on port 80 and 443 from the Interwebs.
29
-Your docker host should have docker and docker-compose installed, and docker running.
32
+Your docker host should have docker, docker-compose, and openssl installed (for testing), and the docker daemon should be running.
30 33
31
-# Stage 1 - get some certs
34
+# Stage 1 - get a cert
32 35
Since this is a greenfield setup, we can let certbot take care of the initial cert request on its own - HAProxy should be down for this.
36
+Create a 'letsencrypt' directory to stuff this in, in your current directory.
33 37
34 38
## Dockerfile
35
-The Dockerfile for the letsencrypt image looks like:
39
+The letsencrypt/Dockerfile file looks like:
36 40
37 41
```dockerfile
38 42
FROM ubuntu:latest
... ...
@@ -47,10 +51,10 @@ COPY deploy-hook /deploy-hook
47 51
RUN chmod +x /deploy-hook
48 52
```
49 53
50
-Note we're installing the docker.io package, and copying in a script. We'll need them later on.
54
+Note we're installing the docker.io package, and copying in a script. We'll need them later on. We could probably use the official certbot image, but chances are you'll already have 'ubuntu:latest' in-cache, so we might as well use it.
51 55
52 56
## deploy-hook
53
-The `deploy-hook` script looks like:
57
+The letsencrypt/deploy-hook script looks like:
54 58
55 59
```sh
56 60
#!/usr/bin/env bash
... ...
@@ -62,12 +66,12 @@ cat /etc/letsencrypt/live/domain1.example.com/fullchain.pem \
62 66
```
63 67
64 68
## docker-compose-stage1.yml
65
-To run the container, we'll wrap it up in a docker-compose file called `docker-compose-stage1.yml`.
69
+To run the container, we'll wrap it up in a docker-compose file called `docker-compose-stage1.yml`. Put this in your current directory:
66 70
67 71
```yaml
68 72
version: '3'
69 73
letsencrypt:
70
- build: .
74
+ build: ./letsencrypt
71 75
image: letsencrypt
72 76
container_name: letsencrypt
73 77
restart: no
... ...
@@ -94,12 +98,12 @@ volumes:
94 98
95 99
Things of note:
96 100
1. certbot listens on port 8000, which docker is mapping to port 80 and making available to the outside world for Let's Encrypt to talk to. We don't need port 443 mapped, because this is an initial request and Let's Encrypt should be fine with just port 80.
97
-2. We're attaching a Volume to /etc/letsencrypt - that's where the certs end up, and that's how we'll make them available to haproxy.
101
+2. We're attaching a docker volume to /etc/letsencrypt - that's where the certs end up, and that's how we'll make them available to haproxy.
98 102
3. The command concatenates the cert chain and private key into a format that haproxy understands, and dumps it out into the mounted /etc/letsencrypt volume.
99 103
4. certbot names the certs for the first domain specified, so that ends up in all of the paths under /etc/letsencrypt. You might be able to change that, but see [rule 1](/rules#love-thy-defaults).
100 104
101 105
## Go!
102
-With all three files in your current directory, run: `docker-compose -f docker-compose-stage1.yml up` and you should hopefully see a message like the following after a couple of seconds:
106
+Run: `docker-compose -f docker-compose-stage1.yml up` and you should hopefully see a message like the following after a couple of seconds:
103 107
104 108
```text
105 109
IMPORTANT NOTES:
... ...
@@ -112,11 +116,13 @@ Certs gotten, and stashed away in a docker volume. Happy days!
112 116
We've got ourselves some certs so it's time to fire up haproxy and enjoy all the HAing and Proxying. Don't know about you, but I am excited.
113 117
114 118
## Dockerfile
115
-We don't need one. Becuase we're using the official image. Because we're adhering to [rule 1](/rules#love-thy-defaults).
119
+We don't need one.
120
+Because we're using the official image.
121
+Because we're adhering to [rule 1](/rules#love-thy-defaults).
116 122
117 123
## haproxy.cfg
118
-Do an `mkdir haproxy/bind`.
119
-Put something like the below in `haproxy/bind/haproxy.fg`:
124
+Do an `mkdir -p haproxy/bind`.
125
+Put something like the below in `haproxy/bind/haproxy.cfg`:
120 126
121 127
```text
122 128
global
... ...
@@ -168,10 +174,10 @@ backend wiki
168 174
server domain2-1 container2:3000 resolvers docker check
169 175
```
170 176
171
-What's going on here?
177
+What's going on here then?
172 178
1. The global section logs everything to stdout, because that's what you do with docker. [rule 6](/rules#thou-shalt-respect-the-sanctity-of-stdout) does not apply in dockerland.
173 179
2. We're setting the Mozilla recommended ciphers and DH values. Check the [current recommendations](https://mozilla.github.io/server-side-tls/ssl-config-generator/) if you're foolish enough to go into production with this stuff.
174
-3. We're using 'resolvers' and 'default-server init-addr none' to get around problem of containers not being up at startup time. Docker with user-defined networks always puts a resolver at 127.0.0.11:53, and haproxy can use that to resolve container names at runtime instead of startup time.
180
+3. We're using 'resolvers' and 'default-server init-addr none' to get around the problem of containers not being up at haproxy startup time. Docker with user-defined networks always puts a resolver at 127.0.0.11:53, and haproxy can use that to resolve container names at runtime instead of startup time.
175 181
4. We're binding to port 8080 and 8443, and setting the cert to the Let's Encrypt cert we dumped out in the previous section. The ports will be mapped back to 80 and 443 by docker later on.
176 182
5. Always redirect to https.
177 183
6. All traffic that matches the certbot [ACME](https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html) challenge protocol is directed to our letsencrypt container (to be created later).