Flask is a simple but powerful Python web framework. During development, a Flask application is available on
localhost:8080. It gets served by Flask’s development web server. But how can you actually make your Flask application available from the web and let other people use your site?
In this post, I will describe this process in detail. My post is primarily directed at ambitious hobby coders who want to show off their projects while keeping expenses low. I will be using a free virtual machine by Google Cloud Platform, Gunicorn as WSGI web server, and nginx as reverse proxy. I expect you to already have your Flask app in a git repository on GitHub.
Setting up the Virtual Machine on Google Cloud Platform
The Google Cloud Platform service offers a free
f1-micro virtual machine that you can use to host your Flask application (see Free on GCP - Google Cloud). With 0.2 vCPUs and 0.6 GB RAM, it is not a very powerful machine, but sufficient for small projects.
Note: All shell commands should be executed on the VM, not on your local machine!
- Create a
f1-micromachine, using Ubuntu 18.04.2 LTS (Minimal Version) as operating system (OS).
- Log in to the machine, e.g. via the Google Cloud Shell.
- Generate an SSH key on the machine (more info):
ssh-keygen -t rsa -b 4096 -C "deploy-key-vm-1"
eval "$(ssh-agent -s)"
- Add the SSH public key to this project as a deploy key on GitHub. So the VM has read-access to this git repository and can pull it.
- Display SSH public key with:
- Add it as a deploy key on
- Clone the repository on the virtual machine:
- Install git with
sudo apt-get install git(for the package install to work, you first need to update Ubuntu’s package lists with
sudo apt-get update)
git clone email@example.com:<username>/<repository>.git
- Install your project’s dependencies. For that, you certainly need to install pip, which is by default not installed in Ubuntu’s minimal version.
sudo apt-get install python3-pip
- Check that the pip binary gets found with
Making the virtual machine available via a (sub-)domain
If you have a domain, I recommend to make the domain or one of its subdomains resolve to your virtual machine. Easiest is to set the (sub)domain’s A-Record to
<VM_IP>. As for domain providers, I use Gandi for important domains, and Domainssaubillig for cheap ones (e.g. 2,28€/year for a .de-domain). But beware, apparently Domainssaubillig is a one-man shop - I would not use it for business-critical things.
Gunicorn as web server
Flask recommends against using
flask run(its development server) for production cases (see here). Therefore we use Gunicorn to run the Flask application for us (more info). Gunicorn is a web application server. It works with the Flask application code as both use the standardized Web Server Gateway Interface (WSGI).
- Add Gunicorn to your project dependencies.
- Start Gunicorn with
gunicorn src.app:app. Note: You need to have your Python virtual environment activated or else the
gunicorncommand will not work. Also,
src.app:appneeds to correspond to your Flask main file path (
src.app) & the start method’s name (
- The webserver is not reachable from outside the VM itself yet. To make the webserver also listen to the outside world, the command is:
gunicorn src.app:app -b 0.0.0.0:8000.
Then, our app is reachable from
<domain>:8000 (if not, you need to change the firewall settings in the Google Cloud Platform VM settings and open port
8000 to the outside). That is already awesome! But now, we want our app to be available on
<DOMAIN>! This means it should be running on port 80 (
nginx as reverse proxy
Having an app listen on port
80 requires special configuration. That is because ports below 1024 are protected by the operating system. We can use the webserver application nginx that proxies outside requests from port
80 to the internal port
8000, where Gunicorn listens.
sudo apt-get install nginx. After it, a
Welcome to nginx!page will appear when you open
<DOMAIN>in your browser.
- We then use the nginx configuration from the Gunicorn documentation and modify it a bit:
Note that you need to set
<DOMAIN> to your own (sub)domain.
We overwrite the file
/etc/nginx/nginx.conf with our
nginx.conf. Restart nginx with
sudo systemctl restart nginx, then view the status with
systemctl status nginx to see if everything went fine.
Now, when we run
gunicorn src.app:app, your domain (
<DOMAIN>) will answer with the Flask webserver response.
Always-on web server
Setting up a background service
You might have noticed it - as soon as you close your command line window, the running gunicorn command gets terminated as well. The web server is not running anymore, and therefore not reachable anymore on
<DOMAIN>. Does this mean that you have to keep your personal computer and the command line open all the time? Of course not, you can keep the process running in background. One way is to set up a service in Ubuntu via
systemd (“System and Service Manager”).
We create a
gunicorn.service file in
/etc/systemd/system (more info):
Note that you need to replace the
<...> placeholders with your respective configuration. Also, you might need to change the
ExecStart command depending on how you installed your project dependencies. In my command, I assume that Pipenv was used. Pipenv is an awesome tool that makes the whole workflow around installing Python pip packages, dependency management, and virtual environments a breeze. I highly recommend it!
Then, add the file
Start the service with
sudo systemctl start gunicorn.service (
systemctl is part of systemd). As you can see in
systemctl status gunicorn.service, the service is now active and your domain should again show your web application. The service stays active when you close the command line, and will stay active as long as the virtual machine is running. Also note that because of the
--reload command flag in
ExecStart, Gunicorn automatically reloads the server if the
app.py file contents change. Without that, we would have to do
sudo systemctl restart gunicorn.service after every file change.
Automatically start the web server on reboot
Right now, the newly created Gunicorn web server service stays active as long as the virtual machine is running. But when the virtual machine gets rebooted, it will not automatically start. In some circumstances, a reboot can happen without you triggering it. By default, GCP can automatically restart your instance, e.g. in case of maintenance events, hardware or software failures (see “Management, security, disks, networking, sole tenancy” settings when creating a VM).
We want our Gunicorn service to automatically start on boot. For that, we need to enable the service. As we can see in
systemctl status gunicorn.service, the service is currently disabled. Disabled means, it won’t automatically start on boot. We can enable the service with
sudo systemctl enable gunicorn.service. A bit of background info: nginx is also running as a service (the service was automatically installed when we installed it). Its service also needs to be enabled for our app to be reachable. Luckily, the service is enabled by default.
To make sure nginx and Gunicorn get automatically started on boot, reboot the VM with
sudo reboot now -h. Then, check the status of the respective services (all should be active). Finally, check that the webserver is accessible by visiting
Securing your website with HTTPS (SSL/TLS certificate)
Before showing off your website to a larger audience, you should seriously consider adding HTTPS by obtaining a SSL certificate. As a starting point, I recommend looking into Let’s Encrypt and their Certbot client. With that, you can easily obtain and configure free SSL certificates. If you followed the instructions in this post, this official tutorial is the way to go.
I will outline in a separate post why HTTPS is useful (UPDATE 2019-07-06: “Why HTTPS is important & how to make your website support it”),
and how to automate the certificate renewal process, something that I find not very well explained in the official documentation.
When configuring Certbot according to the official tutorial, Certbot will automatically set up automatic renewal. While this is a very neat feature and saves us some work, this was not well-documented until recently (at least not on the official tutorial page). The in-detail user-guide as well as some GitHub issues (#5398, #5034) give more context on this. If your Certbot did not come with automatic renewal, it will message you a couple of weeks before the SSL certificate expires. Then, you should set up a cronjob manually. This is the code I use:
In this post, I have shown how to host a Python Flask web application for free on a Google Cloud Platform virtual machine, using GitHub as code repository platform, Gunicorn as web server, and nginx as reverse proxy server.
In the end, whenever we push new stuff in our project to GitHub, we just have to do a
git pull on the VM and the website gets updated! 😊🎉
I hope you enjoyed this post and could make use of it. In case you have questions or remarks, just send me an e-mail to firstname.lastname@example.org. I am looking forward to it!
- Customizing my shell: From bash to zsh to fish
- JSON5: JSON with comments (and more!)
- jq: Analyzing JSON data on the command line
- Get Total Directory Size via Command Line
- Changing DNS server for Huawei LTE router
- Notes on Job & Career Development
- Adding full-text search to a static site (= no backend needed)
- Generating a random string on Linux & macOS
- Caddy web server: Why use it? How to use it?
- Tailwind CSS: A Primer