By the end of this chapter, you should be able to:
Let's start in the terminal:
mkdir deploy_flask cd deploy_flask mkvirtualenv flask-heroku workon flask-heroku pip install flask createdb flask-heroku
And create a simple
app.py with the following:
from flask import Flask import os app = Flask(__name__) @app.route('/') def hello(): return "Hello World!" # If we are in production, make sure we DO NOT use the debug mode if os.environ.get('ENV') == 'production': debug = False else: debug = True if __name__ == '__main__': app.run(debug=debug)
When we deploy an application in production, we will always want to use a server that is production ready and not meant for just development. The server we will be using is gunicorn so let's make sure we run
pip install gunicorn.
When our application is deployed by Heroku, we need to tell heroku what packages to install. Heroku expects a file called
requirements.txt where it will find all the necessary dependencies of our application. To create this file, we can simply run
pip freeze > requirements.txt. It's a good idea to do this in general for projects you're working on, especially if your code is on GitHub; it's an easy way for other developers to identify which packages to install if they fork and clone your work!
When we push our code to Heroku, we need to tell Heroku what command to run to start the server. This command must be placed in a file called
Procfile. Make sure this file does not have any extension and begins with a capital
P. We can run the following command from the Terminal to do this
echo web: gunicorn app:app > Procfile
To make sure you are using a certain version of Python on Herkou, add a file called
runtime.txt and specify the version of Python you want to use. We can do this with
echo python-3.6.2 > runtime.txt
We first need to download the Heroku - you can do that with
brew install heroku
Once you have installed it and created an account on heroku.com, you should be able to run the following commands.
git init git add . git commit -m "initial commit" heroku login heroku create NAME_OF_APP git remote -v # make sure you see heroku git push heroku master heroku ps:scale web=1 # make sure you add a dyno (worker) to your application to create a process for heroku to run heroku open
To debug an application in production, our best bet is to take a look at the server logs using the following command:
heroku logs -t
Make sure you have another terminal window open to do this as it will show you the trailing logs (as new requests come in, it will change). This is very similar to what you would see in your terminal when developing locally. In the logs you will see requests coming in and how your server is responding. You will also find any errors here when your application crashes so make sure to run
heroku logs -t if your application has crashed!
Now that we have an application set up, let's make sure we have an environment variable set so that we are not running in debug mode. Let's also add an environment variable for our SECRET_KEY for when we build secure forms.
# lets create an environment variable for our environment! heroku config:set ENV=production heroku config:set SECRET_KEY=shhhh
In order to use a production database, we need to have heroku create one for us using the following command.
heroku addons:create heroku-postgresql:hobby-dev
Now that we have a postgres database, we need to make sure that we are connecting to the correct database when in production!
# If we are in production, make sure we DO NOT use the debug mode if os.environ.get('ENV') == 'production': app.config['DEBUG'] = False # Heroku gives us an environment variable called DATABASE_URL when we add a postgres database app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') else: app.config['DEBUG'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://localhost/flask-heroku'
Since we are in different environments when developing versus in production, we'll need to use conditional logic to determine how our application runs. In production, we never ever want to have
debug=True because if an error occurs, we do not want all our users seeing the entire stack trace and friendly flask debugger - that could be a real security vulnerability. The same goes for our database as well - in production we will be connecting to a different database.
If your apps have a database, you will have to run your migrations on heroku in order to have all of the tables that your app needs. To run a command on the heroku machine, use
heroku run. So to get your database setup, run:
heroku run python manage.py db upgrade
As our applications grow, and we add more and more conditional logic for different environments, things can start to get messy. A good refactor for this is to actually use some principles from Object Oriented Programming - inheritance and polymorphism.
What we mean by this, is that we can create a base class with some initial configuration and create subclasses which inherit from the base class with slight modifications. To do this, we will create another file in the root of our application called
config.py and then back in our
app.py we will configure our application from the settings in
config.py. You can read more about that here.
Here's what our config.py might look like -
import os class Config(): DEBUG = False TESTING = False SQLALCHEMY_DATABASE_URI = 'postgres://localhost/flask-heroku' class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True
Notice here that we are creating a base class
Config and then creating quite a few other subclasses for each of our environments, with some slight modifications.
Now in our
app.py, we can refactor the following code to look like:
## Our old conditional logic which will only get more and more messy.... # if os.environ.get('ENV') == 'production': # app.config['DEBUG'] = False # app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL') # else: # app.config['DEBUG'] = True # app.config['SQLALCHEMY_DATABASE_URI'] = 'postgres://localhost/flask-heroku' # one line to configure our application! if os.environ.get('ENV') == 'production': app.config.from_object('config.ProductionConfig') # notice here that we are configuring from a file called "config" and a class inside called "ProductionConfig" else: app.config.from_object('config.DevelopmentConfig') # notice here that we are configuring from a file called "config" and a class inside called "DevelopmentConfig"
As we have more and more differences between our environments, we can isolate that logic in a
config.py file and remove any messiness from our
app.py. Try to refactor your code to use a
config.py file and use
app.config_from_object to create your application settings!
When you include external files and assets, they MUST be served over
https, otherwise Heroku will not serve the files.
If you'd like to see an example of deploying a Flask application, feel free to watch the screencast below.
Deploy two Flask applications that you've built in this unit!