You now have learned about all the fundamental concepts of building a web application with Flask. You can create routes, views, and models, connect a database, perform CRUD operations, and paginate your content. In future exercises, weâll learn to put all of that together and refine some more aspects of it. But in this exercise, weâre going to learn about the final step of building a basic application: uploading it to the internet.
Choosing a Host
Flask is a Python framework. So when you think about where to host your application, you should make sure that platform lets you run Python 3 applications. This means shared hosting providers that only let you run PHP on the server are unsuitable. Also, static hosts and object storage (like Netlify or AWS S3) cannot run your application.
Providers like Google App Engine or Digital Ocean require a little more setup. By all means, try it out, and there are lots of great tutorials out there to help you deploy your Flask application to almost any of those providers.
In this tutorial, my focus is to help you learn Flask and not server administration. So I chose to go with render.com as it offers a free tier and is one of the easiest PaaS (platform-as-a-service) providers out there. This may change in the future, though. Especially when it comes to free products, make sure not to build your business on one specific provider. They could always change their business model, and you should be prepared for that.
đĄ A prominent example of a a company changing their business model is Heroku. A previous version of this tutorial (and many other tutorials) used to use Heroku.com for free hosting. They used to offer a free plan and a very simple deployment process. Thatâs how they became very popular and eventually got bought. So in 2022, they announced to stop their free plan. This is just one example and a good lesson on being aware of whom your application (and maybe business) is dependent on.
The Web Server
In order to run a backend application, you need to have a web server software installed and running on the server. This can be something like Apache or ngnix. If you remember from before, Flask actually has a very simple web server software built right into the framework. But thatâs not suitable to run on an actual production server.
Apache and nginx are very popular general-purpose web servers. You cannot use them directly with Python, though. They are just not designed to do that. Instead, you need a so-called WSGI server. Thatâs a web server software designed to support Python applications (among other programming languages as well). Some popular ones are called uWSGI and Gunicorn (inspired by Rubyâs unicorn server).
Many people will still use both something like nginx and a WSGI server in combination to still benefit from the features that come with nginx. It is totally fine, though, to only use a WSGI server for smaller-scale applications. So thatâs what weâre going to do.
If you want to learn more about the topic check out this article.
Weâre going to go with Gunicorn.
To install it, run pip install gunicorn
while your virtual environment is active.
(Donât forget to run pip freeze > requirements.txt
! This is particularly important now because once you deploy your application to a server, youâll need the requirements.txt file to install all the package dependencies on the server.)
To use Gunicorn to run your web server, instead of running python run.py
in the terminal, you just run:
gunicorn run:app
đĄ Important note for Windows users
Gunicorn is designed to run on Unix systems. This refers to distributions of Linux and macOS. It does not run on Windows. Thatâs not much of a problem right now, though, because the purpose of using Gunicorn is to run it on the web server. On your personal computer you can continue to use the default Flask server with
python run.py
.Alternatively to Gunicorn you could also use another web server that does run on Windows such as uWSGI or Waitress. Either of those are fine. The reason we use Gunicorn in the tutorial is because of its popularity.
The run
is the name of your run.py file. app
is the app
variable within that file. This tells the Gunicorn server where to find your application.
Thatâs it. Now you have your application running with the Gunicorn web server instead of the built-in Flask server. (Note that the port of the IP might be different. Instead of 127.0.0.1:5000 it might be 127.0.0.1:8000. Check the output in the Terminal to see the link to your application.)
While you develop your application, that doesnât really matter much. You can continue to use the built-in Flask server. But you can also use it locally. It can be a good idea in larger applications to use the same software on your computer as you do on the server. This way, you catch issues specific to, e.g., the web server early.
đȘ” Logging with Gunicorn
After switching to Gunicorn you may notice that logging like this
current_app.logger.info('Some text')
does not work anymore. Thatâs because Gunicorn requires you to explicitely configure how logging should be handled. Thatâs generally a good idea because on a production server you usually want fine-grained control over how logs are handled. In large applications with a lot of traffic, logs can get pretty long pretty fast. So you usually use either an external logging service or come up with a way of deleting old logs.For now, however, letâs make sure the logs are working in the first place. To do that, open your run.py file. In there, we have this condition
if __name__ == "__main__":
that checks if the app is run directly by callingpython run.py
. We would like to add an additional condition that is only true if the application is run using Gunicorn. Gunicorn automatically sets an environment variable called"GUNICORN_CMD_ARGS"
. So we can check if that variable is present and configure the logger only in that case.At the top of the run.py file import
logging
(which we need for the logger configuration) andos
(which we need to read environment variables).import logging import os
Then, add an
elif
to the aforementioned condition and add the logger configuration like this:import logging import os if __name__ == "__main__": app.run() elif "GUNICORN_CMD_ARGS" in os.environ: gunicorn_logger = logging.getLogger("gunicorn.error") app.logger.handlers = gunicorn_logger.handlers app.logger.setLevel(gunicorn_logger.level)
You can read about the details of configuring the Flask logger in the official documentation. But essentially, what weâre doing, is to connect the built-in Gunicorn logger with the logs of your application and making sure that it dynamically sets the right logging âlevelâ (i.e., error, warning, info, etc.)
After making those updates donât forget to commit those changes with git and upload them to a remote git host (like GitHub, Gitlab, etc.). This will become very important in a minute because render.com uses git for deployment.
If you havenât used git until now run the following commands in your project folder:
git init
git add .
git commit -am 'initial commit'
Then, find a host like GitHub or GitLab and upload your repository there.
Deployment to render.com
Render.com is a hosting provider like many others. One reason I picked it for this tutorial is that it offers a free plan and is easy to use.
First, go to render.com and create a free account. If youâre using GitHub or GitLab to host your project it might be a good idea to already connect your account since thatâs what you need to do later on anyway.
Once you end up on the dashboard you can click the âNew +â button at the top and select âWeb Serviceâ. If you have already paired your GitHub/Gitlab account youâll now see your repositories show up in the middle. If they donât show up you may have to click âConfigure accountâ on the right and manually allow access to specific repositories.
Once your account is connected properly you should be able to find your code repository in the middle of the screen and click the âConnectâ button.
Next, youâll have to pick a name for your project (that will also be the URL to your app) and a few more things. Most fields should already contain the right information.
- Region: Pick one that makes sense based on where you expect your users to be from
- Branch: Probably already picked by default the one that your main version is on
- Root Directory: Should stay empty if youâve been following the tutorial and donât have a custom subfolder for your app
- Build Command: This needs to be updated! It probably already shows the right command to install packages from the requirements.txt file. But what we also want to do is add the migration script here. This makes sure the migrations also run every time automatically when the app is deployed. So update the line to this:
pip install -r requirements.txt; flask db upgrade
- Start Command: This also likely needs to be adjusted. If you have a run.py file that starts your server youâll need to change this line to the following:
gunicorn run:app
Further down you can keep the âFreeâ instance type selected and finally click âCreate Web Serviceâ at the bottom.
Now your application code will automatically be pulled from GitHub/Gitlab. Render.com takes care of installing the right version of Python 3, installing all the packages, and running the migrations. Usually, youâd have to do all these things by hand.
â ïž Your code will now start deploying and installing the application on render.com. But since weâre still missing a few steps, the application will probably fail! Thatâs ok. Donât worry. In fact it really should fail at this point. Look through the logs and try to understand the error. You may have to scroll up a bit and read carefully to find some helpful information. Can you guess what weâre missing?
The Database
As mentioned in previous exercises, you canât use sqlite on providers like render.com because all records are stored in a file, and render.com doesnât let you write data to the file system.
Instead, weâre going to use the SQL database called Postgres on render.com. Postgres is a separate piece of software running on your server. You can think of it as another mini web app with its own web server and URL.
For Flask to work with Postgres you need to install the package psycopg2
. Thatâs quite straight-forward, though:
pip install psycopg2
pip freeze > requirements.txt
(Donât forget to commit those changes with git and upload them to GitHub/Gitlab!)
Thatâs it. no additional configuration required. Now, SQLAlchemy just needs to be told the URL to the database and itâll be able to work with Postgres.
Remember, in our .env file we defined the DATABASE_URL
to be 'sqlite:///database.db'
. Thatâs the URL of our sqlite database. So thatâs the URL we want to be different on render.com.
But first, we actually need a database on render.com. To create a new database go to the render.com dashboard and click on âNew +â at the top. Select âPostgreSQLâ. You can give it any name you like. As a region, you should select the same one as the region of the application. The rest you can leave as is.
Finally, click âCreate Databaseâ. Once created, you should end up on an âInfoâ page about the database and when you scroll down you should see a section with information about âConnectionsâ. One of the points is âInternal Database URLâ. There is a little button to reveal the URL and another button to copy it. Copy that entire URL (make sure not to miss a part)!
Environment Variables
Now go back to the render.com dashboard. Click on your application. Then, in the sidebar, click on âEnvironmentâ. Thatâs where you can define the environment variables on the server. Remember the .env file in our project? This should be part of the .gitignore file. So it was (hopefully!) never deployed to GitHub/Gitlab or render.com. But we still need those variables. So this is the place where you can set those variables on render.com.
As a âkeyâ add DATABASE_URL
and as a value paste the URL you just copied from the database earlier. (Donât be surprised. It will be hidden shortly afterward.)
Looking at that URL, youâll notice that it starts with postgres://
. SQLAlchemy, however, is configured, to automatically try and prefix a Postgres database URL with postgresql://
. So in order to make the database connection work with SQLAlchemy you need to make a small change to the URL you just copied and pasted. Change the beginning of the text to this:
postgresql://
Everything afterward has to stay the same.
Next, make sure to add the other environment variables and their values (you find the keys and values in the .env file of your project.) Those should probably be FLASK_APP
(with the value run.py
) and SECRET_KEY
. Do not set a FLASK_DEBUG
variable! Thatâs very important.
đĄ You may have other environment variables (such as an
API_KEY
from the API-building exercise). Make sure to add all the environment variables that are required to run your code.Youâll probably also add new environment variables in the future - for example, when you implement authentication. Donât forget that youâll need to then also add the environment variables before you deploy the application.
Finally, click âSave Changesâ and click âManual Deployâ at the top right. This should redeploy the application.
Watch the logs to see how things go and if any other errors come up. But technically now everything should be ready to go.
You can click on the URL to your website on the top left once the deployment is done and click around to see if everything works.
đĄ Debugging on render.com
On render.com you can click on the âLogsâ tab on the left to see the logs of the server. This is extremely helpful whenever something doesnât work. Scroll through the list and read carefully so you donât miss anything. Usually, youâll find any error messages here thatâll tell you what went wrong.
Running a script on the server
If you click around your website and have been following the tutorial, youâll probably notice quickly that the cookie records are not there. That makes sense. We created a brand new database on the server. All the cookie records we worked with before were in our local database on our computer.
Remember, at one point we created a seed file, a script to populate our database with a few cookie records. Locally, we executed that script by running python -m app.scripts.seed
. And if you had a paid plan with render.com you could do the same thing through the âShellâ on the render.com dashboard. But weâre on a free plan. So we have to get a bit creative.
To make this fully clear: The following approach is a hacky workaround. In a real-world application, you should go with a paid plan and use either the âShellâ in the browser, a âJobâ, or an SSH connection to execute scripts on the server.
So what you can do as a hack is just add a secret route in your application temporarily. When that route is called in a web browser, the seed script is executed. This is obviously not a secure solution! So you should make sure this can only be called once and ideally you just remove the route immediately afterward again.
To make sure the route can only be called once letâs add it to the cookies blueprint. So in /cookies/routes.py, at the bottom add this route:
@blueprint.route('/run-seed')
def run_seed():
if not Cookie.query.filter_by(slug='chocolate-chip').first():
import app.scripts.seed
return 'Database seed completed!'
else:
return 'Nothing to run.'
This adds a route /run-seed
to your application. The file should already import the Cookie
model at the top. Using the model we can check if a record with the slug chocolate-chip
already exists. If your seed contains different files, you should adjust this line to check if a record from the seed file already exists. Any record should be fine.
The condition makes sure to only run the line import app.scripts.seed
(and therefore execute the seed script) if a record with the given slug does not yet exist. If it does find a record with that slug, it will not do anything and just return âNothing to runâ. This makes sure the seed can only be executed once.
đĄ Make sure the seed file, the seed file uses the condition
if __name__ == '__main__':
to only runcreate_app()
if the file is executed directly. This is now very important because weâll execute the script within the context of a running application.
After adding this route, you can commit the changes using git and upload them to GitHub/Gitlab. This should automatically trigger a deployment to render.com. Once the deployment is done, you should now be able to access the /run-seed
route on the server (using the URL you got from the render.com dashboard).
If everything went well you should now see either Database seed completed!
the first time you access that route on the server. The second time you should see Nothing to run.
. And on the /cookies
route, you should now see the cookies created.
If something went wrong check the logs on render.com. They should help you identify what the issue is.
đ To debug this locally, rename or delete the database.db file in your project folder. Then run
flask db upgrade
to generate a new blank database locally. Now, start the server locally and try to access http://127.0.0.1:8000/run-seed. Try to use the logs and error messages to debug the issue.
Side Note on localhost and 127.0.0.1
If it wasnât clear, I just wanted to point out again: localhost
and 127.0.0.1
refer to the IP of your own local computer. Donât ever hard-code them anywhere in your code. The URL to your application, once itâs deployed to Heroku, is a different one. Thatâs why itâs best to always work with either relative paths like /cookies
or the url_for()
function.
đ Practice
You now know everything to build and deploy a full Flask application!
- To practice what you have learned in this exercise, set up Heroku with Postgres for another application and deploy it.
- Make sure to connect Postgres properly and run the migrations (and seed files if applicable).
- Watch the logs come in as you navigate around your website.