Running your application over HTTPS with traefik
I just read another very clear article from Miguel Grinberg about Running Your Flask Application Over HTTPS.
As the title suggests, it describes different ways to run a flask application over HTTPS. I have been using flask for quite some time, but I didn't even know about the ssl_context argument. You should definitively check his article!
Using nginx as a reverse proxy with a self-signed certificate or Let’s Encrypt are two options I have been using in the past.
If your app is available on the internet, you should definitively use Let's Encrypt. But if your app is only supposed to be used internally on a private network, a self-signed certificate is an option.
Traefik
I now often use docker to deploy my applications. I was looking for a way to automatically configure Let's Encrypt. I initially found nginx-proxy and docker-letsencrypt-nginx-proxy-companion. This was interesting but wasn't that straight forward to setup.
I then discovered traefik: "a modern HTTP reverse proxy and load balancer made to deploy microservices with ease". And that's really the case! I've used it to deploy several applications and I was impressed. It's written in go, so single binary. There is also a tiny docker image that makes it easy to deploy. It includes Let's Encrypt support (with automatic renewal), websocket support (no specific setup required)... And many other features.
Here is a traefik.toml configuration example:
defaultEntryPoints = ["http", "https"] [web] # Port for the status page address = ":8080" # Entrypoints, http and https [entryPoints] # http should be redirected to https [entryPoints.http] address = ":80" [entryPoints.http.redirect] entryPoint = "https" # https is the default [entryPoints.https] address = ":443" [entryPoints.https.tls] # Enable ACME (Let's Encrypt): automatic SSL [acme] # Email address used for registration email = "test@traefik.io" storageFile = "/etc/traefik/acme/acme.json" entryPoint = "https" onDemand = false OnHostRule = true # Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge [acme.httpChallenge] entryPoint = "http" # Enable Docker configuration backend [docker] endpoint = "unix:///var/run/docker.sock" domain = "example.com" watch = true exposedbydefault = false
With this simple configuration, you get:
HTTP redirect on HTTPS
Let's Encrypt support
Docker backend support
UPDATE (2018-03-04): as mentioned by @jackminardi in the comments, Let's Encrypt disabled the TLS-SNI challenges for most new issuance. Traefik added support for the HTTP-01 challenge. I updated the above configuration to use this validation method: [acme.httpChallenge].
A simple example
I created a dummy example just to show how to run a flask application over HTTPS with traefik and Let's Encrypt. Note that traefik is made to dynamically discover backends. So you usually don't run it with your app in the same docker-compose.yml file. It usually runs separately. But to make it easier, I put both in the same file:
version: '2' services: flask: build: ./flask image: flask command: uwsgi --http-socket 0.0.0.0:5000 --wsgi-file app.py --callable app labels: - "traefik.enable=true" - "traefik.backend=flask" - "traefik.frontend.rule=${TRAEFIK_FRONTEND_RULE}" traefik: image: traefik volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik/traefik.toml:/etc/traefik/traefik.toml:ro - ./traefik/acme:/etc/traefik/acme ports: - "80:80" - "443:443" - "8080:8080"
Traefik requires access to the docker socket to listen for changes in the backends. It can thus automatically discover when you start and stop containers. You can ovverride default behaviour by using labels in your container.
Supposing you own the myhost.example.com domain and have access to ports 80 and 443 (you can setup port forwarding if you run that on your machine behind a router at home), you can run:
$ git clone https://github.com/beenje/flask_traefik_letsencrypt.git $ cd flask_traefik_letsencrypt $ export TRAEFIK_FRONTEND_RULE=Host:myhost.example.com $ docker-compose up
Voilà! Our flask app is available over HTTPS with a real SSL certificate!
Traefik discovered the flask docker container and requested a certificate for our domain. All that automatically!
Traefik even comes with a nice dashboard:
With this simple configuration, Qualys SSL Labs gave me an A rating :-)
Not as good as the A+ for Miguel's site, but not that bad! Especially considering there isn't any specific SSL setup.
A more realistic deployment
As I already mentioned, traefik is made to automatically discover backends (docker containers in my case). So you usually run it by itself.
Here is an example how it can be deployed using Ansible:
--- - name: create traefik directories file: path: /etc/traefik/acme state: directory owner: root group: root mode: 0755 - name: create traefik.toml template: src: traefik.toml.j2 dest: /etc/traefik/traefik.toml owner: root group: root mode: 0644 notify: - restart traefik - name: create traefik network docker_network: name: "{{traefik_network}}" state: present - name: launch traefik container with letsencrypt support docker_container: name: traefik_proxy image: "traefik:{{traefik_version}}" state: started restart_policy: always ports: - "80:80" - "443:443" - "{{traefik_dashboard_port}}:8080" volumes: - /etc/traefik/traefik.toml:/etc/traefik/traefik.toml:ro - /etc/traefik/acme:/etc/traefik/acme:rw - /var/run/docker.sock:/var/run/docker.sock:ro # purge networks so that the container is only part of # {{traefik_network}} (and not the default bridge network) purge_networks: yes networks: - name: "{{traefik_network}}" - name: force all notified handlers to run meta: flush_handlers
Nothing strange here. It's quite similar to what we had in our docker-compose.yml file. We created a specific traefik_network. Our docker containers will have to be on that same network.
Here is how we could deploy a flask application on the same server using another ansible role:
- name: launch flask container docker_container: name: flask image: flask command: uwsgi --http-socket 0.0.0.0:5000 --wsgi-file app.py --callable app state: started restart_policy: always purge_networks: yes networks: - name: "{{traefik_network}}" labels: traefik.enable: "true" traefik.backend: "flask" traefik.frontend.rule: "Host:myhost.example.com" traefik.port: "5000"
We make sure the container is on the same network as the traefik proxy. Note that the traefik.port label is only required if the container exposes multiple ports. It's thus not needed in our example.
That's basically it. As you can see, docker and Ansible make the deployment easy. And traefik takes care of the Let's Encrypt certificate.
Conclusion
Traefik comes with many other features and is well documented. You should check this Docker example that demonstrates load-balancing. Really cool.