Configuring HTTPS - Method 1

  • no third party script required.

To use HTTPS, you need to make some changes to the previous configuration to use a certificate and change the URIs protocol from HTTP to HTTPS. Of course, there are several other methods, but the one illustrated here just continues the above configuration.

Getting a Certificate

First you need a certificate. Get it from somewhere (e.g. https://letsencrypt.org), and prepare it in these formats:

  • CER with KEY.
  • PFX: you can convert a CER (or other format) certificate with its key file into a PFX file using the Linux openssl tool, like this:
openssl pkcs12 -export -out certificate.pfx -inkey <NAME>.key -in <NAME>.cer

Here your mileage may vary according to the format you get for the certificate. See the openssl manual for more, e.g. to convert PEM into CRT and KEY:

openssl x509 -outform der -in fullchain.pem -out docker_it.crt
openssl rsa -outform der -in privkey.pem -out docker_it.key

Then, place the 3 files under some folder in your host, e.g.:

  • my CER and KEY files are under opt/cadmus/cert.
  • my PFX file is under opt/cadmus/nginx.

This is required because we are going to share these certificates with the services running inside the Docker containers via a Docker volume. Using a volume is the suggested way of making a certificate available to what’s inside a Docker image, without having to build an image with the certificate inside it, which would not be secure, nor practical.

Passing Certificates to the API

The next step is passing our certificates to the API service inside the Cadmus Docker stack.

As an ASP.NET 6 API, the Cadmus API service uses Kestrel.

(1) in the API service of the Docker compose script add a volume to share the PFX certificate file:

volumes:
  - /opt/cadmus/cert/certificate.pfx:/app/Infrastructure/Certificate/certificate.pfx

This instruction means that the file certificate.pfx under folder /opt/cadmus/cert in our host machine will be shared via a Docker volume pointing to file /app/Infrastructure/Certificate/certificate.pfx in the Docker image file system. This location is where Kestrel (the web server used by Cadmus API) will be instructed to find the certificate.

(2) continuing in the Docker compose script, add these environment variables in the same API service section of the Docker script, to tell Kestrel about the certificate:

environment:
  - ASPNETCORE_URLS=https://+:443;http://+:80
  - ASPNETCORE_Kestrel__Certificates__Default__Path=/app/Infrastructure/Certificate/certificate.pfx
  - ASPNETCORE_Kestrel__Certificates__Default__Password=YOURCERTPASSWORD

Of course, replace YOURCERTPASSWORD with the password used for your certificate.

These variables tell Kestrel that the HTTPS port is 443, the HTTP port is 80, the certificate is found at the specified path, and its password is that specified.

💥 Mind the number of underscore (_) characters in these variable names! Please note that ASPNETCORE_ ends with 1 underscore, whereas all the other underscores in the variable name come with 2 of them.

(3) still in this environment section, be sure to add an HTTPS (rather than HTTP) allowed origin for CORS, e.g.:

  - ALLOWEDORIGINS__0=https://docker.somewhere.it

The suffix __0 here is just the index in the array of allowed origins. So here it happens to be 0, because it’s the first origin I put in my list. Should you have more entries, change this index accordingly, remembering that it’s 0-based (so the first origin you add is ALLOWEDORIGINS__0, the second ALLOWEDORIGINS__1, etc.).

This is enough for the API service: all what we did was sharing our PFX certificate file with Kestrel running in the Docker container, and telling it where to find this certificate.

Also, given that our frontend web app will be served in HTTPS, we ensured that its URI is among the allowed origins for this API.

Passing Certificates to the Frontend

The frontend app uses NGINX to serve the web app. You can find the default NGINX configuration for it among the source files of the Cadmus web app, in its GitHub repository.

(1) rather than modifying this configuration and rebuild the image, we’re going to replace it with a new one, using our CER and KEY certificates. In the new NGINX configuration, the server section is replaced with this one:

server {
  listen 443 ssl;
  listen 80;
  server_name localhost;
  ssl_certificate docker_it.cer;
  ssl_certificate_key docker_it.key;

  gzip on;
  gzip_min_length 1000;
  gzip_proxied expired no-cache no-store private auth;
  gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }
}

The relevant changes are:

  • listen 443 ssl for HTTPS.
  • the ssl_certificate lines to define the CER and KEY certificates to be used.

The rest of the section is unchanged. Save this new nginx.conf configuration file somewhere in your host, e.g. in /opt/cadmus/nginx.

(2) in the Docker compose script, head to the cadmus-app service section, and make these changes:

  • ensure you have changed the image name to reference your production version for the app. The default image name in the GitHub source refers to the development version, which targets API in localhost. Your production version should change the env.js file so that API are located at their HTTPS URIs.
  • ensure that both ports 80 and 443 are exposed.
  • add volumes to pass the NGINX configuration and the CER and KEY files to the Docker container.

Here is a sample:

cadmus-app:
  restart: always
  image: vedph2020/cadmus-app:0.0.10-prod
  ports:
    - 80:80
    - 443:443
  depends_on:
    - cadmus-api
    - cadmus-db
  networks:
    - cadmus-network
  volumes:
    # https://www.techrepublic.com/article/how-to-enable-ssl-on-nginx/
    # overwrite nginx.conf to use SSL with certificates from the host
    - /opt/cadmus/nginx/nginx.conf:/etc/nginx/nginx.conf
    - /opt/cadmus/nginx/docker_it.cer:/etc/nginx/docker_it.cer
    - /opt/cadmus/nginx/docker_it.key:/etc/nginx/docker_it.key

Configuring HTTPS - Method 2

Among others, an easier configuration option is using the acme-companion, which relies on Docker API to automate most of the certificate procedures: essentially, once it is in place, you just have to start the Docker compose script to have this script automatically request and apply a certificate from LetsEncrypt, and renewing it when required.

Setup DNS

The first step is getting a domain for your site, so that LetsEncrypt can see it. Let’s assume that the domain name is mydomain.org, and that you want to setup the API endpoint at api.mydomain.org, and the application at app.mydomain.org. You should setup your hosting environment accordingly (e.g. adding A -alias- records to the DNS configuration, pointing to the server’s IP address via RDATA), e.g.:

  • A = api.mydomain.org, RDATA=99.100.101.102.
  • A = app.mydomain.org, RDATA=99.100.101.102.

Setup Acme Companion

This summarizes the tutorial at https://blog.ssdnodes.com/blog/host-multiple-ssl-websites-docker-nginx, with changes I introduced to use the more recent dockerized version of the ACME companion, which further simplifies the process.

(1) create and enter the directory where to place a Docker compose script:

mkdir nginx-proxy
cd nginx-proxy

(2) create a network to be shared among containers:

docker network create nginx-proxy

(3) in the folder created at (1), create a docker-compose.yml file for NGINX. This will be used as the reverse proxy which redirects traffic in your host machine.

version: '2'

# version 2 required by volumes_from
# https://github.com/nginx-proxy/acme-companion/blob/main/docs/Docker-Compose.md

services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    restart: always
    volumes:
      - conf:/etc/nginx/conf.d
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro

  acme-companion:
    image: nginxproxy/acme-companion
    container_name: nginx-proxy-acme
    volumes_from:
      - nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - acme:/etc/acme.sh
    environment:
      - DEFAULT_EMAIL=daniele.fusi@unive.it

volumes:
  conf:
  vhost:
  html:
  certs:
  acme:

# Do not forget to 'docker network create nginx-proxy' before launch, and to add '--network nginx-proxy' to proxied containers.

networks:
  default:
    name: nginx-proxy
    external: true

(4) run the script:

docker compose up -d

To confirm, run docker ps. You should see 2 running containers with these names:

  • nginx-proxy
  • nginx-proxy-acme

This procedure is done once. When in place, you can use this infrastructure to add as many docker-based services as you want, each with its own subdomain and certificate.

Modify Docker Compose Script

To set up any containerized app to work with this proxy, you must configure its compose script as follows:

  • add 3 environment variables: VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL.
  • replace the Docker network (with nginx-proxy, cf. step 2 in the previous section).
  • exposing port 80/443 (this is already done in the default script).

With reference to the default docker compose script, you should make these changes:

(1) in the cadmus-api section, add the above environment variables (under environment), e.g. (of course change their values to fit your own setup):

cadmus-api:
  # ... omitted for brevity
  environment:
    - VIRTUAL_HOST=api.mydomain.org
    - LETSENCRYPT_HOST=api.mydomain.org
    - LETSENCRYPT_EMAIL=myemail@mydomain.org
    - ALLOWEDORIGINS__0=https://app.mydomain.org

(2) in the cadmus-app section, do the same for the app subdomain:

cadmus-app:
  # ... omitted for brevity
  environment:
    - VIRTUAL_HOST=app.mydomain.org
    - LETSENCRYPT_HOST=app.mydomain.org
    - LETSENCRYPT_EMAIL=myemail@mydomain.org

(3) in all the sections, remove the original network of the script as we’re going to replace it with nginx-proxy. This means deleting (or commenting out) these lines from cadmus-db, cadmus-api, cadmus-app; and deleting (or commenting out) the final cadmus-network

  • remove from any service:
    networks:
      - cadmus-network
  • remove at the end:
networks:
  cadmus-network:
    driver: bridge

(4) at the end, add the nginx-proxy network instead (note the added external value here):

networks:
  default:
    external: true
    name: nginx-proxy

(5) rebuild the Angular app (follow the instructions in its README.md), and then under dist/env.js (mind dist here!) change the URI of the API according to your new setup, and the version variable as you prefer, e.g.:

(function (window) {
  window.__env = window.__env || {};
  window.__env.apiUrl = "https://api.mydomain.org/api/";
  window.__env.version = "0.0.11-prod";
})(this);

(6) build and push a Docker image for your production environment, e.g.:

docker build . -t vedph2020/cadmus-app:0.0.11-prod
docker push vedph2020/cadmus-app:0.0.11-prod

(7) change the name of the app image accordingly in the cadmus-app section:

cadmus-app:
  image: vedph2020/cadmus-app:0.0.11-prod

(8) start the docker compose script, and in some moments you will be able to navigate to your HTTPS domain for both API and app.

▶️ next: backup