Deploying to Production with Traefik

So, you have your new app running locally and you want to show it off to the world. The next step is to get it deployed and in this article I will show you how to cover the following requirements easily using docker and Traefik.

  • Use an edge terminating SSL proxy so your applications can just use http behind the proxy
  • Support for http2 with no extra effort
  • No hassle SSL certificate acquisition and renewal
  • Minimum grade A security rating from Qualys SSL Labs
  • Easily handle multiple applications through the same proxy

Prerequisites

You will need a public Linux VM (I am using Azure) with docker installed - for more info on how to do this follow the instructions in the Docker docs.

Traefik

I prefer to have my proxy in a separate git repository to my applications because then I can add whatever additional new applications I like in the future independent of the proxy (mostly).

So, firstly create a new git repository and an empty traefik directory.

> git init host-proxy

> cd host-proxy

> mkdir traefik

> cd traefik

Create a new file called traefik.yml and paste in the configuration below (make sure to add your email address!).

log:
  level: "WARN"

providers:
  docker:
    exposedByDefault: false
  file:
    directory: "/etc/traefik/dynamic"

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

certificatesResolvers:
  lets-encrypt:
    acme:
      storage: "/etc/traefik/acme.json"
      email: [ENTER YOUR EMAIL ADDRESS HERE]
      tlsChallenge: {}

This tells traefik where to look for any dynamic content, to listen on ports 80 and 443 and to use lets encrypt for SSL certificates. Notice the json file referenced here. You should create that file but leave it empty for now, you will need to grant read write permissions on your VM to this file later.

> touch acme.json

Now we need to create the dynamic configuration file that will force and configure SSL for all of your services.

> mkdir dynamic

> cd dynamic

Create the file https.yml and paste the following content into it.

http:
  routers:
    force-https:
      entryPoints: 
      - "http"
      middlewares: 
      - "force-https"
      - "testHeader"
      rule: "HostRegexp(`{any:.+}`)"
      service: "noop"

  middlewares:
    force-https:
      redirectScheme: 
        scheme: "https"
    testHeader:
      headers:
        frameDeny: true
        sslRedirect: true
        stsPreload: true

  services:
    # noop service, the URL will be never called
    noop:
      loadBalancer:
        servers:
        - url: "http://192.168.0.1"
tls:
  options:
    default:
      minVersion: "VersionTLS12"
    mintls13:
      minVersion: "VersionTLS13"

This ensures that all http requests are forced to use https and to set the HSTS header which ensures that browsers will use https for all future requests. It also ensures that the server uses either the TLS 1.2 or 1.3 protocol and that the server will prefer it's ciphers over the clients.

Finally we need a docker-compose file to finish up the magic.

> cd ../../

Create the file docker-compose.yml and paste in the following.

version: '3.4'
services:
  traefik:
    image: traefik
    restart: always
    ports:
      - '80:80'
      - '443:443'
    volumes:
    - ./traefik:/etc/traefik
    - /var/run/docker.sock:/var/run/docker.sock:ro

That's it for the proxy. Just push your repository to a remote source control, log on to your VM and pull the repository and compose it up.

> ssh my-remote-server

> git pull host-proxy

> cd host-proxy

Remember the acme.json from earlier?

> chmod 600 acme.json

Finally...

> docker-compose up -d

If you docker ps you should see your traefik proxy up and running.

Register your Application with Traefik

This part is now deceptively simple, all you need to do is to add the following labels to your application compose file and replace the {YOUR SERIVCE NAME HERE} and {YOUR DOMAIN NAME HERE} with your values.

version: '3.4'

# ~~SNIP~~

labels:
  - 'traefik.enable=true'
  - 'traefik.http.routers.{YOUR SERIVCE NAME HERE}.rule=Host(`{YOUR DOMAIN NAME HERE}`, `www.{YOUR DOMAIN NAME HERE}`)'
  - 'traefik.http.routers.{YOUR SERIVCE NAME HERE}.tls=true'
  - 'traefik.http.routers.{YOUR SERIVCE NAME HERE}.tls.certresolver=lets-encrypt'

When you docker-compose up -d your service traefik will automatically register it, ensure you have a valid SSL certificate from lets encrypt and start routing https traffic to your service.

Feel free to submit you url to Qualys SSL Labs and you should get an A rating.

If you curl https://yoururl.com you will also see it's using http2.

You can add as many additional services as your server can handle and traefik will manage it all for you.

I will come back to traefik in another post on more of the awesome things it can do for you.

Full Example Application Compose File

version: '3.4'

services:
  demo-app-mariadb:
    image: mariadb
    restart: always
    ports: 
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: shh-it's-secret
      MYSQL_DATABASE: MyDatabase
      MYSQL_USER: NoobUser
      MYSQL_PASSWORD: lots-of-random-chars
    volumes:
     - /datadrive/demo-app-mariadb:/var/lib/mysql
  
  demo-app:
    image: myawesomereg/myawesomereg:demo-app
    restart: always
    build:
      context: .
      dockerfile: demo-app/Dockerfile
    ports:
      - "61405:80"
    depends_on:
      - "demo-app-mariadb"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - MYSQL_USERID=NoobUser
      - MYSQL_PASSWORD=lots-of-random-chars
      - MYSQL_SERVER=demo-app-mariadb
      - MYSQL_DATABASE=MyDatabase
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.demo-app.rule=Host(`demo-app.co.uk`, `www.demo-app.co.uk`)'
      - 'traefik.http.routers.demo-app.tls=true'
      - 'traefik.http.routers.demo-app.tls.certresolver=lets-encrypt'
Richard Andrews - 15/01/2021