Getting encryption from your end-user to your load balancer with SSL Termination is usually a piece of cake, and for most applications, it's good enough. But how much more effort is it to get complete end-to-end encryption all the way down to the container?

I assume, for simplicity's sake, that either the load balancer or some other mechanism is forcing the HTTP -> HTTPS upgrades, so all inbound connections to the container are already HTTPS.


NGINX Configuration

First things first, we need to update the NGINX configuration to listen on port 443 and have an SSL Certificate configured.

For this example, I am assuming that you have a PHP-FPM container downstream, but the same concepts can be applied to anything.

server {
    listen 443 ssl;
    index index.php index.html;
    root /var/www/public;
    
    ssl_certificate /etc/ssl/certs/certificate.pem;
    ssl_certificate_key /etc/ssl/private/privatekey.pem;

    location / {
        try_files $uri /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_buffers 16 16k; 
        fastcgi_buffer_size 32k;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

SSL Certificates

Now that your web server is expecting a certificate, we have to get it one. I'll walk through two methods of doing this: within the Dockerfile (as an entrypoint) and via Kubernetes ConfigMaps.


SSL Certificates via Dockerfile

While not recommended for production use, this is an ideal option for more ephemeral environments (e.g. local or CI/CD) as every container spun up creates its certificate. The downside, however, is that the container boot-up time increases.

Your new Dockerfile will look something like this:

FROM nginx:alpine

WORKDIR /etc/nginx
RUN apk add openssl

ADD generate-certs.sh /etc/nginx/generate-certs.sh
RUN chmod +x /etc/nginx/generate-certs.sh
ADD vhost.conf /etc/nginx/conf.d/default.conf

CMD /bin/sh -c /etc/nginx/generate-certs.sh && nginx -g 'daemon off;'

We need to install openssl and then call the generate-certs.sh file which will generate the private key and certificate:

#!/bin/sh

openssl \
  req -out /etc/ssl/private/certificate.csr \
  -new -newkey rsa:2048 -nodes -keyout /etc/ssl/private/privatekey.pem \
  -subj "/C=US/ST=OR/L=Portland/O=Docker/CN=my.domain.com"
openssl \
  req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
   -keyout /etc/ssl/private/privatekey.pem \
   -out /etc/ssl/private/certificate.crt \
  -subj "/C=US/ST=OR/L=Portland/O=Docker/CN=my.domain.com"

SSL Certificates via Kubernetes ConfigMaps

If you prefer to have the same certificate across all containers or otherwise centrally manage where the certificate comes from, you can use a ConfigMap created from files and mount it to the container

kubectl -n charts create configmap nginx-ssl --from-file=/folder/containing/cert-and-privatekey
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: ##CONTAINER_IMAGE##
          imagePullPolicy: Always
          ports:
            - containerPort: 443
          volumeMounts:
            - name: nginx-ssl
              mountPath: /etc/ssl/private/certificate.pem
              readOnly: true
              subPath: certificate.pem
            - name: nginx-ssl
              mountPath: /etc/ssl/private/privatekey.pem
              readOnly: true
              subPath: privatekey.pem
          resources:
            requests:
              cpu: 250m
            limits:
              cpu: 500m
      volumes:
        - name: nginx-ssl
          configMap:
            name: nginx-ssl

Complete, functional examples can be found here: