Adding SSL to a Flask application running inside a Docker container

Wessel Huising
5 min readOct 25, 2021

--

This article will explain how SSL encryption can be added to your Flask application, using a Gunicorn server, running inside a Docker container on your instance connected to the World Wide Web. I used a subdomain for my own application, but this approach also works for root domains.

Everybody wants the green lock for their domain.

This post is part of the series that explains how to go from an AI use-case (generating horoscopes using text-generation) to an actual working web-application in Flask, querying the AI model using an API built in fastAPI. Take into account that this article is written from the same perspective and thus uses some of its examples and variables.

Certbot

This post is using a package called Certbot to generate the digital certificates for the webserver. Certbot is used to easily obtain and configure a free certificate from Let’s Encrypt. It necessary to run the Certbot commands from the server itself, as Certbot will generate the keys needed for that particular webserver.

Install Certbot on the server by executing sudo apt-get install certbot. Whenever your instance does not use APT as a package manager, go find your flavour.

Gunicorn

Why Gunicorn? Because we are running a productionized server, dummy. As Flask comes with their own WSGI development server, it does advise you to use another WSGI HTTP server like Gunicorn. Of course it is possible to install server software on your instance directly but the whole point of this post is to use Docker. So that’s why we are using Gunicorn in the Docker container to serve the Flask application, in a “productionized” manner.

Standalone certification

Because we are serving the Flask application within a Docker container (so not directly on the instance itself but within a virtual instance) we do need a so-called “standalone” certificate. I will paraphrase the Certbot docs:

Use standalone mode to obtain a certificate if you don’t want to use (or don’t currently have) existing server software. The standalone plugin does not rely on any other server software running on the machine where you obtain the certificate.

Let’s create the standalone certificate for the right domain. In the case of the horoscope project that would be horoscope.wesselhuising.nl. Make sure to execute the following commands on the server instance, running the future Docker container:

sudo certbot certonly -d horoscope.wesselhuising.nl -n — standalone

This command will create the certification files in the following locations:

/etc/letsencrypt/live/horoscope.wesselhuising.nl/fullchain.pem/etc/letsencrypt/live/horoscope.wesselhuising.nl/privkey.pem

Port 443

Don’t forget to open the port 443 on the server hosting the Docker container. The HTTPS protocol is using port 443 by default (they fought for this one) so by blocking the connection; I guess I don’t have to explain. Execute the following code on the server instance:

sudo ufw allow 443

Talisman

To make sure the Flask application auto-redirects the incoming requests using the old and lame HTTP protocol to the sexy HTTPS protocol a package called Talisman is brought to life. Obviously it has more amazing features but to go over them it will take at least three more blogs so I decided to skip it for now.

Install Talisman using pip install flask-talismanwithin your environment which will be replicated by the Docker container when running the Flask app.

Make sure to wrap the app in your code with the Talisman Class. An example of the code can be found here. Set the content_security_policy to None as it will block the use of stylesheets (there is a way to whitelist the right files but for now, this is it).

Dockerfile

To make sure that the Gunicorn server is using the right certificates the CMD command should be updated in the Dockerfile. As the /etc/letsencrypt/ directory on the server will be binded to the Docker container, the paths will stay the same.

CMD [“python3”, “-m”, “gunicorn”, “-w”, “4”, “-b”, “0.0.0.0:5000”, 
"--certfile”, “/etc/letsencrypt/live/horoscope.wesselhuising.nl/fullchain.pem”,
"--keyfile”, “/etc/letsencrypt/live/horoscope.wesselhuising.nl/privkey.pem”, “app:app”]

In this command the port that will be serving the Flask application is set to 5000. This is indeed not the 443 port that is used by the HTTPS protocol but port-forwarding will do the trick (so forward 5000 to 443). See the embedded github.com link for the full example of the Dockerfile.

Bindings

Last step is to adjust the Dockerfile to mount the right stand-alone certificates into the Docker container and make sure the certification are used by the Gunicorn WSGI HTTP server.

volumes:
- /etc/letsencrypt:/etc/letsencrypt

In the example of the horoscope project, the project is using Docker compose to spin up two containers. Within the docker-compose.yml the directory of the certifictates is mounted as a volume. Use the following code or see the example on github.

In case you don’t want to use the docker-compose approach, it is of-course possible to mount the right path using the command-line interface using the
-v or --mount options.

Whenever you have more certifications for multiple applications on the same server, you might want to consider adding the directory of set domain /live/horoscope.wesselhuising.nl to the binding of the volume path for super duper security.

Renew

As the certificates made by Certbot are expiring every three moths, as an administrator you should make sure to renew those certificates every once in a while (so every three months). You could consider writing a cronjob executing following code:

certbot renew

Enjoyed the article? Please give me a clap and a follow if you really feel like it. Please don’t hesitate to put any questions in the comment section.

--

--

Wessel Huising

Randomly Picked Stack Engineer. Amsterdam, slowly becoming older.