How to use Let's Encrypt with Docker container

2019-03-09 10:16发布

I am running an Express-based website in a Docker container based on the Node.js image. How do I use Let's Encrypt with a container based on that image?

5条回答
聊天终结者
2楼-- · 2019-03-09 10:55

You may have a look here : https://certbot.eff.org/docs/using.html?highlight=docker#running-with-docker

Then what I personally do is :

  1. Create a Docker volume to store the certs and generate the certs with the above image
  2. Create a Docker user-defined network (https://docs.docker.com/engine/userguide/networking/#/user-defined-networks)
  3. Create an image based on nginx with your configuration (maybe this will be useful)
  4. Create a Nginx container based on your image, mount the volume in it and connect it to the network (also forward port 80 and 443 to whatever you want)
  5. I would create a container for your node.js app and connect it to the same network

Now if you configured nginx correctly (point to the right path for the TLS certs and proxy to the right URL, like http://my-app:3210) you should have access to your app in https.

查看更多
萌系小妹纸
3楼-- · 2019-03-09 11:00

The first thing I've done is to create a simple express-based docker image.

I am using the following app.js, taken from express's hello world example in their docs:

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

I also ended up with the following packages.json file after running their npm init in the same doc:

{
  "name": "exampleexpress",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.14.0"
  }
}

I've created the following Dockerfile:

FROM node:onbuild
EXPOSE 3000
CMD node app.js

Here's the output when I do my docker build step. I've removed most of the npm install output for brevity sake:

$ docker build -t exampleexpress .
Sending build context to Docker daemon 1.262 MB
Step 1 : FROM node:onbuild
# Executing 3 build triggers...
Step 1 : COPY package.json /usr/src/app/
Step 1 : RUN npm install
 ---> Running in 981ca7cb7256
npm info it worked if it ends with ok
<snip>
npm info ok
Step 1 : COPY . /usr/src/app
 ---> cf82ea76e369
Removing intermediate container ccd3f79f8de3
Removing intermediate container 391d27f33348
Removing intermediate container 1c4feaccd08e
Step 2 : EXPOSE 3000
 ---> Running in 408ac1c8bbd8
 ---> c65c7e1bdb94
Removing intermediate container 408ac1c8bbd8
Step 3 : CMD node app.js
 ---> Running in f882a3a126b0
 ---> 5f0f03885df0
Removing intermediate container f882a3a126b0
Successfully built 5f0f03885df0

Running this image works like this:

$ docker run -d --name helloworld -p 3000:3000 exampleexpress
$ curl 127.0.0.1:3000
Hello World!

We can clean this up by doing: docker rm -f helloworld


Now, I've got my very basic express-based website running in a Docker container, but it doesn't yet have any TLS set up. Looking again at the expressjs docs, the security best practice when using TLS is to use nginx.

Since I want to introduce a new component (nginx), I'll do that with a second container.

Since nginx will need some certificates to work with, let's go ahead and generate those with the letsencrypt client. The letsencrypt docs on how to use letsencrypt in Docker can be found here: http://letsencrypt.readthedocs.io/en/latest/using.html#running-with-docker

Run the following commands to generate the initial certificates. You will need to run this on a system that is connected to the public internet, and has port 80/443 reachable from the letsencrypt servers. You'll also need to have your DNS name set up and pointing to the box that you run this on:

export LETSENCRYPT_EMAIL=<youremailaddress>
export DNSNAME=www.example.com

docker run --rm \
    -p 443:443 -p 80:80 --name letsencrypt \
    -v "/etc/letsencrypt:/etc/letsencrypt" \
    -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
    quay.io/letsencrypt/letsencrypt:latest \
    certonly -n -m $LETSENCRYPT_EMAIL -d $DNSNAME --standalone --agree-tos

Make sure to replace the values for LETSENCRYPT_EMAIL and DNSNAME. The email address is used for expiration notifications.


Now, let's set up an nginx server that will make use of this newly generated certificate. First, we'll need an nginx config file that is configured for TLS:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /dev/stdout  main;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  _;
        return 301 https://$host$request_uri;
    }

    server {
        listen              443 ssl;
        #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        server_name         www.example.com;
        ssl_certificate     /etc/letsencrypt/live/www.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;

        location ^~ /.well-known/ {
            root   /usr/share/nginx/html;
            allow all;
        }

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://expresshelloworld:3000;
        }
    }
}

We can put this config file into our own custom nginx image with the following Dockerfile:

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf

This can be build with the following command: docker build -t expressnginx .

Next, we'll create a custom network so we can take advantage of Docker's service discovery feature:

docker network create -d bridge expressnet

Now, we can fire up the helloworld and nginx containers:

docker run -d \
    --name expresshelloworld --net expressnet exampleexpress
docker run -d -p 80:80 -p 443:443 \
    --name expressnginx --net expressnet \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /usr/share/nginx/html:/usr/share/nginx/html \
    expressnginx

Double check that nginx came up properly by taking a look at the output of docker logs expressnginx.

The nginx config file should redirect any requests on port 80 over to port 443. We can test that by running the following:

curl -v http://www.example.com/

We should also, at this point, be able to make a successful TLS connection, and see our Hello World! response back:

curl -v https://www.example.com/

Now, to set up the renewal process. The nginx.conf above has provisions for the letsencrypt .well-known path for the webroot verification method. If you run the following command, it will handle renewal. Normally, you'll run this command on some sort of cron so that your certs will be renewed before they expire:

export LETSENCRYPT_EMAIL=me@example.com
export DNSNAME=www.example.com

docker run --rm --name letsencrypt \
    -v "/etc/letsencrypt:/etc/letsencrypt" \
    -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
    -v "/usr/share/nginx/html:/usr/share/nginx/html" \
    quay.io/letsencrypt/letsencrypt:latest \
    certonly -n --webroot -w /usr/share/nginx/html -d $DNSNAME --agree-tos
查看更多
贼婆χ
4楼-- · 2019-03-09 11:04

Front end - NGINX - which listening 443 port, and proxies to beck end

Back end - you docker container

查看更多
兄弟一词,经得起流年.
5楼-- · 2019-03-09 11:05

I've recently implemented https with let's encrypt using nginx. I'm listing the challenges I've faced, and the way I've implemented step-by-step here.

Challenge:

  1. Docker file system is ephemeral. That means after each time you make a build the certificates that are stored or if generated inside the container, will vanish. So it's very tricky to generate certificates inside the container.

Steps to overcome it:

Below guide is independent of kind of the app you have, as it only involves nginx and docker.

  • First install nginx on you server (not on container, but directly on the server.) You can follow this guide to generate certificate for your domain using certbot.
  • Now stop this nginx server and start the build of your app. Install nginx on your container and open port 80, 443 on your docker container. (if using aws open on ec2 instance also as by default aws open only port 80)

  • Next run your container and mount the volumes that contain certificate file directly on the container. I've answered a question here on how to do the same.

  • This will enable https on your app. Incase you are not able to observe, and are using chrome try clearing dns cache for chrome

Auto renewal process :

  • Let's encrypt certificates are valid only for 3 months. In the above guide steps to configure auto renewal is also setup. But you've to stop and restart your container every 3 months atleast to make sure the certificates mounted on your docker container are up to date. (You will have to restart the nginx server we set up in the first step to make the renewal happen smoothly)
查看更多
仙女界的扛把子
6楼-- · 2019-03-09 11:14

There are many ways to achieve this depending on your setup. One popular way is to setup nginx in front of your Docker container, and handle the certificates entirely within your nginx config.

The nginx config can contain a list of 'usptreams' (your Docker containers) and 'servers' which essentially map requests to particular upstreams. As part of that mapping you can also handle SSL.

You can use certbot to help you set this up.

查看更多
登录 后发表回答