2013-04-24T06:24:58Z

The Flask Mega-Tutorial, Part XVIII: Deployment on the Heroku Cloud

(Great news! There is a new version of this tutorial!)

This is the eighteenth article in the series in which I document my experience writing web applications in Python using the Flask microframework.

The goal of the tutorial series is to develop a decently featured microblogging application that demonstrating total lack of originality I have decided to call microblog.

NOTE: This article was revised in September 2014 to be in sync with current versions of Python and Flask.

Here is an index of all the articles in the series that have been published to date:

In the previous article we explored traditional hosting options. We've looked at two actual examples of deployment to Linux servers, first to a CentOS system and later to the Raspberry Pi credit card sized computer. Those that are not used to administer a Linux system probably thought the amount of effort we had to put into the task was huge, and that surely there must be an easier way.

Today we will try to see if deploying to the cloud is the answer to the complexity problem.

But what does it mean to "deploy to the cloud"?

A cloud hosting provider offers a platform on which an application can run. All the developer needs to provide is the application, because the rest, which includes the hardware, operating system, scripting language interpreters and database, is managed by the service.

Sounds too good to be true, right?

We'll look at deploying to Heroku, one of the most popular cloud hosting services. I picked Heroku not only because it is popular, but also because it has a free service level, so we get to host our application without having to spend any money. If you want to find information about this type of services and what other providers are out there you can consult the Wikipedia page on platform as a service.

Hosting on Heroku

Heroku was one of the first platform as a service providers. It started as a hosting option for Ruby based applications, but then grew to support many other languages like Java, Node.js and our favorite, Python.

In essence, deploying a web application to Heroku requires just uploading the application using git (you'll see how that works in a moment). Heroku looks for a file called Procfile in the application's root directory for instructions on how to execute the application. For Python projects Heroku also expects a requirements.txt file that lists all the module dependencies that need to be installed.

After the application is uploaded you are essentially done. Heroku will do its magic and the application will be online within seconds. The amount of money you pay directly determines how much computing power you get for your application, so as your application gets more users you will need to buy more units of computing, which Heroku calls "dynos", and that is how you keep up with the load.

Ready to try Heroku? Let's get started!

Creating Heroku account

Before we can deploy to Heroku we need to have an account with them. So head over to heroku.com and create an account.

Once you are logged in you have access to a dashboard, where all your apps can be managed. We will not be using the dashboard much though, but it provides a nice view of your account.

Installing the Heroku client

Even though it is possible to manage applications from the Heroku web site to some extent, there are some things that can only be done from the command line, so we'll just do everything there.

Heroku offers a tool called the "Heroku client" that we'll use to create and manage our application. This tool is available for Windows, Mac OS X and Linux. If there is a Heroku toolbelt download for your platform then that's the easiest way to get the Heroku client tool installed.

The first thing we should do with the client tool is to login to our account:

$ heroku login

Heroku will prompt for your email address and your account password. The first time you login it will send your public SSH key to the Heroku servers.

Your authenticated status will be remembered in subsequent commands.

Git setup

The git tool is core to the deployment of apps to Heroku, so it must also be available. If you installed the Heroku toolbelt then you already have it as part of that installation.

To deploy to Heroku the application must be in a local git repository, first so let's get one set up:

$ git clone -b version-0.18 git://github.com/miguelgrinberg/microblog.git
$ cd microblog

Note that we are choosing a specific branch to be checked out, this is the branch that has the Heroku integration.

Creating a Heroku app

To create a new Heroku app you just use the create command from the root directory of the application:

$ heroku apps:create flask-microblog
Creating flask-microblog... done, stack is cedar
http://flask-microblog.herokuapp.com/ | git@heroku.com:flask-microblog.git

In addition to setting up a URL this command adds a git remote to our git repository that we will soon use to upload the application.

Of course the name flask-microblog is now taken by me, so make sure you use a different app name if you are doing this along.

Eliminating local file storage

Several of the functions of our application rely on writing data to disk files.

Unfortunately we have a tricky problem with this. Applications that run on Heroku are not supposed to write permanent files to disk, because Heroku uses a virtualized platform that does not remember data files, the file system is reset to a clean state that just contains the application script files each time a virtual worker is started. Essentially this means that the application can write temporary files to disk, but should be able to regenerate those files should they disappear. Also when two or more workers (dynos) are in use each gets its own virtual file system, so it is not possible to share files among them.

This is really bad news for us. For starters, it means we cannot use sqlite as a database, and our Whoosh full text search database will also fail to work, since it writes all its data to files. We also have the compiled translation files for Flask-Babel, which are generated when running the tr_compile.py script. And yet another area where there is problem is logging, we used to write our logfile.to the tmp folder and that is also not going to work when running on Heroku.

We have identified four major problems for which we need to try to find solutions.

For our first problem, the database, we'll migrate to Heroku's own database offering, which is based on PostgreSQL.

For the full text search functionality we don't have a readily available alternative. We could re-implement full text searches using PostgreSQL functionality, but that would require several changes to our application. It is a pity, but solving this problem now would be a huge distraction, so for now we'll disable full text searches when running under Heroku.

To support translations we are going to include the compiled translation files in the git repository, that way these files will be persistant in the file system.

Finally, since we can't write our own log file, we'll add our logs to the logger that Heroku uses, which is actually simple, since Heroku will add to its log anything that goes to stdout.

Creating a Heroku database

To create a database we use the Heroku client:

$ heroku addons:add heroku-postgresql:dev
Adding heroku-postgresql:dev on flask-microblog... done, v3 (free)
Attached as HEROKU_POSTGRESQL_ORANGE_URL
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pgbackups:restore.
Use `heroku addons:docs heroku-postgresql:dev` to view documentation.
$ heroku pg:promote HEROKU_POSTGRESQL_ORANGE_URL
Promoting HEROKU_POSTGRESQL_ORANGE_URL to DATABASE_URL... done

Note that we are adding a development database, because that is the only database offering that is free. A production web server would need one of the production database options.

And how does our application know the details to connect to this database? Heroku publishes the URI to the database in the $DATABASE_URL environment variable. If you recall, we have modified our configuration to look for this variable in the previous hosting article, so the changes are already in place to connect with this database.

Disabling full text searches

To disable full text searches we need our application to be able to know if it is running under Heroku or not. For this we will add a custom environment variable, again using the Heroku client tool:

heroku config:set HEROKU=1

The HEROKU environment variable will now be set to 1 when our application runs inside the Heroku virtual platform.

Now it is easy to disable the full text search index. First we add a configuration variable (file config.py):

# Whoosh does not work on Heroku
WHOOSH_ENABLED = os.environ.get('HEROKU') is None

Then we suppress the creation of the full text database instance (file app/models.py):

from config import WHOOSH_ENABLED

enable_search = WHOOSH_ENABLED
if enable_search:
    import flask_whooshalchemy as whooshalchemy

# ...
if enable_search:
    whooshalchemy.whoosh_index(app, Post)

Precompiled Tranlations

This one is pretty easy. After running tr_compile.py we end up with a <language>.mo file for each <language>.po source file. All we need to do is add the mo files to the git repository, and then in the future we'll have to remember to update them. The mo file for Spanish is included in the branch of the git repository dedicated to this article.

Fixing the logging

Under Heroku, anything that is written to stdout is added to the Heroku application log. But logs written to a disk file will not be accessible. So on this platform we will suppress the file log and instead use a log that writes to stdout (file app/__init__.py):

if not app.debug and os.environ.get('HEROKU') is None:
    import logging
    from logging.handlers import RotatingFileHandler
    file_handler = RotatingFileHandler('tmp/microblog.log', 'a', 1 * 1024 * 1024, 10)
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('microblog startup')

if os.environ.get('HEROKU') is not None:
    import logging
    stream_handler = logging.StreamHandler()
    app.logger.addHandler(stream_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('microblog startup')

The web server

Heroku does not provide a web server. Instead, it expects the application to start its own server on the port number given in environment variable $PORT.

We know the Flask web server is not good for production use because it is single process and single threaded, so we need a better server. The Heroku tutorial for Python suggests gunicorn, a pre-fork style web server written in Python, so that's the one we'll use.

For our local environment gunicorn installs as a regular python module into our virtual environment:

$ flask/bin/pip install gunicorn

To start this browser we need to provide a single argument that names the Python module that defines the application and the application object, both separated by a colon. Now for example, if we wanted to start a local gunicorn server with this module we would issue the following command:

$ flask/bin/gunicorn --log-file - app:app
2013-04-24 08:42:34 [31296] [INFO] Starting gunicorn 19.1.1
2013-04-24 08:42:34 [31296] [INFO] Listening at: http://127.0.0.1:8000 (31296)
2013-04-24 08:42:34 [31296] [INFO] Using worker: sync
2013-04-24 08:42:34 [31301] [INFO] Booting worker with pid: 31301

The requirements file

Soon we will be uploading our application to Heroku, but before we can do that we have to inform the server what dependencies the application needs to run. We created a requirements.txt file in the previous chapter, to simplify the installation of dependencies in a dedicated server, and the good news is that Heroku also imports dependencies from a requirements file.

The gunicorn web server needs to be added to the list, and so is the psycopg2 driver, which is required by SQLAlchemy to connect to PostgreSQL databases. The final requirements.txt file looks like this:

Babel==1.3
Flask==0.10.1
Flask-Babel==0.9
Flask-Login==0.2.11
Flask-Mail==0.9.0
Flask-OpenID==1.2.1
Flask-SQLAlchemy==2.0
Flask-WTF==0.10.2
Flask-WhooshAlchemy==0.56
Jinja2==2.7.3
MarkupSafe==0.23
SQLAlchemy==0.9.7
Tempita==0.5.2
WTForms==2.0.1
Werkzeug==0.9.6
Whoosh==2.6.0
blinker==1.3
coverage==3.7.1
decorator==3.4.0
flipflop==1.0
guess-language==0.2
gunicorn==19.1.1
itsdangerous==0.24
pbr==0.10.0
psycopg2==2.5.4
python-openid==2.2.5
pytz==2014.7
six==1.8.0
speaklater==1.3
sqlalchemy-migrate==0.9.2
sqlparse==0.1.11

Some of these modules will not be needed in the Heroku version of our application, but it really doesn't hurt to have extra stuff, to me it seems better to have a complete requirements list.

The Procfile

The last requirement is to tell Heroku how to run the application. For this Heroku requires a file called Procfile in the root folder of the application.

This file is extremely simple, it just defines process names and the commands associated with them (file Procfile):

web: gunicorn app:app
init: python db_create.py
upgrade: python db_upgrade.py

The web label is associated with the web server. Heroku expects this task and will use it to start our application.

The other two tasks, named init and upgrade are custom tasks that we will use to work with our application. The init task initializes our application by creating the database. The upgrade task is similar, but instead of creating the database from scratch it upgrades it to the latest migration.

Deploying the application

And now we have reached the most interesting part, where we push the application to our Heroku hosting account. This is actually pretty simple, we just use git to push the application:

$ git push heroku version-0.18
Counting objects: 307, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (168/168), done.
Writing objects: 100% (307/307), 165.57 KiB, done.
Total 307 (delta 142), reused 272 (delta 122)

-----> Python app detected
-----> No runtime.txt provided; assuming python-2.7.4.
-----> Preparing Python runtime (python-2.7.4)
-----> Installing Distribute (0.6.36)
-----> Installing Pip (1.3.1)
-----> Installing dependencies using Pip (1.3.1)
...
-----> Discovering process types
       Procfile declares types -> init, upgrade, web

-----> Compiled slug size: 29.6MB
-----> Launching... done, v6
       http://flask-microblog.herokuapp.com deployed to Heroku

To git@heroku.com:flask-microblog.git
 * [new branch]      master -> master

The label heroku that we used in the git push command was automatically registered with our git repository when we created our application with heroku create. To see how this remote repository is configured you can run git remote -v in the application folder.

The last argument, version-0.18 is the name of the branch we are deploying to Heroku. For your own applications, you will likely use master here.

The first time we push the application to Heroku we need to initialize the database and the translation files, and for that we can execute the init task that we included in our Procfile:

$ heroku run init
Running `init` attached to terminal... up, run.7671
/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/engine/url.py:105: SADeprecationWarning: The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'. The new URL format is postgresql[+driver]://<user>:<pass>@<host>/<dbname>
  module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects

The deprecation warning comes from SQLAlchemy, because it does not like that the URI starts with postgres:// instead of postgresql://. This URI comes from Heroku via the $DATABASE_URL environment variable, so we really don't have any control over this. Let's hope this continues to work for a long time.

Believe it or not, now the application is online. In my case, the application can be accessed at http://flask-microblog.herokuapp.com. For example, you can become my follower from my profile page. I'm not sure how long I'll leave it there, but feel free to give it a try if you can connect to it!

Updating the application

The time will come when an update needs to be deployed. This works in a similar way to the initial deployment. First the application is pushed from git. If your application is on the master branch, an update can be pushed with:

$ git push heroku master

Then the upgrade script is executed:

$ heroku run upgrade

Logging

If a problem occurs then it may be useful to see the logs. Recall that for the Heroku hosted version we are writing our logs to stdout which Heroku collects into its own logs.

To see the logs we use the Heroku client:

$ heroku logs

The above command will show all the logs, including Heroku ones. To only see application logs we can use this command:

$ heroku logs --source app

Things like stack traces and other application errors will appear in these app logs.

Is it worth it?

We've now seen what it takes to deploy to a cloud hosting service so we can now compare against the traditional hosting.

The simplicity aspect is easily won by cloud. At least for Heroku the deployment process was extremely simple. When deploying to a dedicated server or VPS there are a lot of administrative tasks that need to be done to prepare the system. Heroku takes care of all that and allows us to concentrate on our application.

The price is where it is harder to come to a conclusion. Cloud offerings are more expensive than dedicated servers, since you are not only paying for the server but also for the admin work. A pretty basic production service with Heroku that includes two dynos and the least expensive production database costs $85 per month at the time I'm writing this. On the other side, if you look hard you can find well provisioned VPS servers for abour $40 per year.

In the end, I think it all comes down to what is most important to you, time or money.

The End?

The updated application is available, as always, on my github page. Alternatively you can download it as a zip file below:

Download microblog 0.18.

With our application deployed in every possible way it feels like we are reaching the end of this journey.

I hope these articles were a useful introduction to the development of a real world web application project, and that the knowledge dump I've made over these eighteen articles motivates you to start your own project.

I'm not closing the door to more microblog articles. If and when an interesting topic comes to mind I will write more, but I expect the rate of updates from now on will slow down a bit. From time to time I may make small updates to the application that don't deserve a blog post, so you may want to watch the project on github to catch these.

I will continue blogging about topics related to web development and software in general, so I invite you to connect via Twitter or Facebook if you haven't done it yet, so that you find my future articles.

Thank you, again, for being a loyal reader.

Miguel

136 comments

  • #101 Jeff C said 2016-08-21T05:06:09Z

    in Python 3.5 I always get this error:

    File "/Users/jeffreyjones/Sites/microblog/flask/lib/python3.5/site-packages/openid/init.py", line 52, in if len(version_info) != 3: TypeError: object of type 'map' has no len()

    still working at it...

  • #102 Jeff Corliss said 2016-08-21T07:40:01Z

    First of all I want to say, amazing tutorial thank you so much.

    Would it be possible to do an updated version for python >3.0 ??

    I am working through some of the kinks but have no deployed successfully.

    thank you!

  • #103 marq said 2016-08-22T18:02:14Z

    Hi Miguel!

    When I run app with gunicorn, then register -> confirm account I got flash message that account confirmed, but still isn't. For app run as python manage.py runserver it works as desired. Do you have any idea why gunicorn can't interpret link with token?

    Thank in advance! MarQ:)

  • #104 onwuzulike emeka said 2016-08-24T09:31:33Z

    great tutorial Mr.Grinberg I am having some trouble testing gunicorn on my local machine. it gives me this error. I am using windows. thanks in advance.

    gunicorn app:app Traceback (most recent call last): File "C:\Python34\lib\runpy.py", line 170, in _run_module_as_main "main", mod_spec) File "C:\Python34\lib\runpy.py", line 85, in _run_code exec(code, run_globals) File "C:\Python34\Scripts\gunicorn.exe__main__.py", line 5, in File "C:\Python34\lib\site-packages\gunicorn\app\wsgiapp.py", line 10, in from gunicorn.app.base import Application File "C:\Python34\lib\site-packages\gunicorn\app\base.py", line 12, in from gunicorn import util File "C:\Python34\lib\site-packages\gunicorn\util.py", line 9, in import fcntl ImportError: No module named 'fcntl'

  • #105 Miguel Grinberg said 2016-08-25T05:14:25Z

    @Jeff: could be an openid incompatibility with Python 3.5.

  • #106 Miguel Grinberg said 2016-08-25T05:15:11Z

    @Jeff: this tutorial was revised and edited to support Python 3.4 a while ago.

  • #107 Miguel Grinberg said 2016-08-25T05:18:04Z

    @onwuzulike: gunicorn does not run on windows.

  • #108 Miguel Grinberg said 2016-08-25T05:22:44Z

    @marq: I could be related to this issue: https://github.com/miguelgrinberg/flasky/issues/58, which turned out to be in Flask itself. See if upgrading Flask to the latest 0.11.x release addresses your problem.

  • #109 kirill said 2016-11-11T15:19:43Z

    Hello, Miguel! Great tutorial, thank you! I think Flask is very cool stuff, trying to implement some of your advices and encountering strange problem, google couldn't give me helpful answer, maybe you can help.

    on localhost everything works fine, on openshift it says: [error] [client xxx] mod_wsgi (pid=230305): Exception occurred processing WSGI script '/var/lib/openshift/../app-root/runtime/repo/run.py'. [error] [client xxx] TypeError: create_app() takes 1 positional argument but 2 were given

    project structure: app main static templates init.py models.py config development.py requirements.txt run.py setup.py

    run.py here, init.py are here: http://pastebin.com/ce9nAxEd

    what is the problem? what it wants from me? thank you!

  • #110 Miguel Grinberg said 2016-11-12T17:08:01Z

    @kirill: you may need to look at the complete stack trace of the error to find where is the offending call to create_app being made.

  • #111 kirill said 2016-11-27T08:11:40Z

    @Miguel i found out some new things about my problem, maybe it is interesting for you or somebody else, encountering the same problem.

    openshift flask runner requires that in run.py (== wsgi.py) app was imported as class instance with name 'application'. so it is wrong (openshift flask standard runner think so) to import function 'create_app' in run.py, which i tried to do and why i've got error with positional args. the only way to solve problem, i found, is to initiate app: 'app = create_app()' without any args, in init.py so it starts on standard config (with no attention to your config files) and redefine config in wsgi.py: 'application.config.from_pyfile(CONFIG_FILE)'

    i'm not sure this is a good way of solving problem, because app starts at first without your config and after you redefine config, restarts. but i don't see another way of defining config in run.py if you use blueprints and your app lives on openshift.

  • #112 Miguel Grinberg said 2016-11-27T18:06:16Z

    @kirill: I don't have experience with OpenShift, so I can't comment in this requirement. But you should load a configuration name from the environment, don't need to always use the default.

  • #113 Jacob said 2016-12-22T22:18:28Z

    Thank you for this awesome tutorial!

    I'm having issues with gunicorn when I put everything on heroku. I'm using windows, but I don't think that it should matter for heroku. I first had problems with my app, and then I used the microblog app that you have on github, just to make sure that I did not miss anything. I get the same error in both cases:

    Running web on flask-thingapp... up, run.4899 (Free) Traceback (most recent call last): File "/app/.heroku/python/bin/gunicorn", line 11, in sys.exit(run()) File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/base.py", line 185, in run super(Application, self).run() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/base.py", line 71, in run Arbiter(self).run() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 169, in run self.manage_workers() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 477, in manage_workers self.spawn_workers() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 542, in spawn_workers time.sleep(0.1 * random.random()) File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 209, in handle_chld self.reap_workers() File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 459, in reap_workers raise HaltServer(reason, self.WORKER_BOOT_ERROR) gunicorn.errors.HaltServer:

    I can't figure out what's causing the error and I would appreciate any help.

  • #114 Miguel Grinberg said 2016-12-22T23:12:22Z

    @Jacob: can you launch gunicorn locally, using the exact same command you have on the Procfile?

  • #115 Manuel Ramos said 2016-12-29T01:16:56Z

    Hi Miguel! Congratulations for this awesome tutorial!

    When trying to enter to the application for the first time through my browser I got an "Application error".

    I checked the logs and the problem is the following:

    Traceback (most recent call last): 2016-12-29T00:58:22.137654+00:00 app[web.1]: File "/app/.heroku/python/bin/gunicorn", line 11, in 2016-12-29T00:58:22.137655+00:00 app[web.1]: sys.exit(run()) 2016-12-29T00:58:22.137657+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run 2016-12-29T00:58:22.137704+00:00 app[web.1]: WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run() 2016-12-29T00:58:22.137735+00:00 app[web.1]: super(Application, self).run() 2016-12-29T00:58:22.137706+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/base.py", line 185, in run 2016-12-29T00:58:22.137762+00:00 app[web.1]: Arbiter(self).run() 2016-12-29T00:58:22.137736+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/base.py", line 71, in run 2016-12-29T00:58:22.137764+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 169, in run 2016-12-29T00:58:22.137819+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 477, in manage_workers 2016-12-29T00:58:22.137801+00:00 app[web.1]: self.manage_workers() 2016-12-29T00:58:22.137881+00:00 app[web.1]: self.spawn_workers() 2016-12-29T00:58:22.137893+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 542, in spawn_workers 2016-12-29T00:58:22.137960+00:00 app[web.1]: time.sleep(0.1 * random.random()) 2016-12-29T00:58:22.137972+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 209, in handle_chld 2016-12-29T00:58:22.138010+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/arbiter.py", line 459, in reap_workers 2016-12-29T00:58:22.138008+00:00 app[web.1]: self.reap_workers() 2016-12-29T00:58:22.138085+00:00 app[web.1]: raise HaltServer(reason, self.WORKER_BOOT_ERROR) 2016-12-29T00:58:22.138138+00:00 app[web.1]: gunicorn.errors.HaltServer: 2016-12-29T00:58:29.415345+00:00 app[web.1]: Traceback (most recent call last): 2016-12-29T00:58:29.415371+00:00 app[web.1]: File "/app/.heroku/python/bin/gunicorn", line 11, in 2016-12-29T00:58:29.415372+00:00 app[web.1]: sys.exit(run()) 2016-12-29T00:58:29.415373+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/gunicorn/app/wsgiapp.py", line 74, in run 2016-12-29T00:58:29.415408+00:00 app[web.1]: WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()

    I tried my best but I have no idea of what the problem is or how to solve it. I would be glad if you could help me with this because it is the very last step! Thank you so much!

    Manuel Ramos Calderón

  • #116 Miguel Grinberg said 2016-12-30T06:42:35Z

    @Manuel: All your log is saying is that gunicorn was unable to boot your application. Try running the same command you have on your Procfile locally and see if that fails too. Also check if there are any previous errors in the log that are in the application's source files. This stack trace that you captured appears to be downstream of the real problem.

  • #117 Manuel Ramos said 2016-12-30T22:32:03Z

    Hello, Miguel! Thanks, I solved the problem with gunicorn and now the app runs with the exact same command that is on the Procfile. So, I committed to github and updated on heroku. Now, instead of the "Application error" I was getting before, now I get a big: "Internal Server Error" on my browser. You can check it by going to: https://microblog-mses.herokuapp.com

    The stack trace is pretty long, and as I don't know where the error is I'll post it entirely: 2016-12-30T22:22:49.692076+00:00 app[web.1]: File "/app/app/views.py", line 32, in before_request 2016-12-30T22:22:49.692076+00:00 app[web.1]: db.session.add(g.user) 2016-12-30T22:22:49.692077+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 150, in do 2016-12-30T22:22:49.692078+00:00 app[web.1]: return getattr(self.registry(), name)(args, kwargs) 2016-12-30T22:22:49.692079+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1490, in add 2016-12-30T22:22:49.692079+00:00 app[web.1]: raise exc.UnmappedInstanceError(instance) 2016-12-30T22:22:49.692080+00:00 app[web.1]: UnmappedInstanceError: Class 'werkzeug.local.LocalProxy' is not mapped 2016-12-30T22:22:55.218314+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:22:55.218330+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/logging/handlers.py", line 930, in emit 2016-12-30T22:22:55.218372+00:00 app[web.1]: smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) 2016-12-30T22:22:55.218388+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 256, in init 2016-12-30T22:22:55.218418+00:00 app[web.1]: (code, msg) = self.connect(host, port) 2016-12-30T22:22:55.218433+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 317, in connect 2016-12-30T22:22:55.218460+00:00 app[web.1]: (code, msg) = self.getreply() 2016-12-30T22:22:55.218475+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 365, in getreply 2016-12-30T22:22:55.218513+00:00 app[web.1]: + str(e)) 2016-12-30T22:22:55.218531+00:00 app[web.1]: SMTPServerDisconnected: Connection unexpectedly closed: timed out 2016-12-30T22:22:55.218533+00:00 app[web.1]: Logged from file app.py, line 1423 2016-12-30T22:22:55.218853+00:00 app[web.1]: Exception on /favicon.ico [GET] 2016-12-30T22:22:55.218855+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:22:55.218856+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app 2016-12-30T22:22:55.218857+00:00 app[web.1]: response = self.full_dispatch_request() 2016-12-30T22:22:55.218858+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request 2016-12-30T22:22:55.218859+00:00 app[web.1]: rv = self.handle_user_exception(e) 2016-12-30T22:22:55.218859+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception 2016-12-30T22:22:55.218860+00:00 app[web.1]: reraise(exc_type, exc_value, tb) 2016-12-30T22:22:55.218861+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1473, in full_dispatch_request 2016-12-30T22:22:55.218861+00:00 app[web.1]: rv = self.preprocess_request() 2016-12-30T22:22:55.218862+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1666, in preprocess_request 2016-12-30T22:22:55.218863+00:00 app[web.1]: rv = func() 2016-12-30T22:22:55.218863+00:00 app[web.1]: File "/app/app/views.py", line 32, in before_request 2016-12-30T22:22:55.218864+00:00 app[web.1]: db.session.add(g.user) 2016-12-30T22:22:55.218865+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 150, in do 2016-12-30T22:22:55.218865+00:00 app[web.1]: return getattr(self.registry(), name)(*args, kwargs) 2016-12-30T22:22:55.218866+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1490, in add 2016-12-30T22:22:55.218867+00:00 app[web.1]: raise exc.UnmappedInstanceError(instance) 2016-12-30T22:22:55.218867+00:00 app[web.1]: UnmappedInstanceError: Class 'werkzeug.local.LocalProxy' is not mapped 2016-12-30T22:23:07.940696+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:23:07.940722+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/logging/handlers.py", line 930, in emit 2016-12-30T22:23:07.940759+00:00 app[web.1]: smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) 2016-12-30T22:23:07.940762+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 256, in init 2016-12-30T22:23:07.940799+00:00 app[web.1]: (code, msg) = self.connect(host, port) 2016-12-30T22:23:07.940802+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 317, in connect 2016-12-30T22:23:07.940841+00:00 app[web.1]: (code, msg) = self.getreply() 2016-12-30T22:23:07.940844+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 365, in getreply 2016-12-30T22:23:07.940874+00:00 app[web.1]: + str(e)) 2016-12-30T22:23:07.940903+00:00 app[web.1]: SMTPServerDisconnected: Connection unexpectedly closed: timed out 2016-12-30T22:23:07.940906+00:00 app[web.1]: Logged from file app.py, line 1423 2016-12-30T22:23:07.941156+00:00 app[web.1]: Exception on / [GET] 2016-12-30T22:23:07.941158+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:23:07.941159+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app 2016-12-30T22:23:07.941159+00:00 app[web.1]: response = self.full_dispatch_request() 2016-12-30T22:23:07.941161+00:00 app[web.1]: rv = self.handle_user_exception(e) 2016-12-30T22:23:07.941160+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request 2016-12-30T22:23:07.941162+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception 2016-12-30T22:23:07.941162+00:00 app[web.1]: reraise(exc_type, exc_value, tb) 2016-12-30T22:23:07.941163+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1473, in full_dispatch_request 2016-12-30T22:23:07.941163+00:00 app[web.1]: rv = self.preprocess_request() 2016-12-30T22:23:07.941164+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1666, in preprocess_request 2016-12-30T22:23:07.941165+00:00 app[web.1]: rv = func() 2016-12-30T22:23:07.941166+00:00 app[web.1]: File "/app/app/views.py", line 32, in before_request 2016-12-30T22:23:07.941166+00:00 app[web.1]: db.session.add(g.user) 2016-12-30T22:23:07.941167+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 150, in do 2016-12-30T22:23:07.941168+00:00 app[web.1]: return getattr(self.registry(), name)(args, kwargs) 2016-12-30T22:23:07.941168+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1490, in add 2016-12-30T22:23:07.941169+00:00 app[web.1]: raise exc.UnmappedInstanceError(instance) 2016-12-30T22:23:07.941170+00:00 app[web.1]: UnmappedInstanceError: Class 'werkzeug.local.LocalProxy' is not mapped 2016-12-30T22:23:13.168557+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:23:13.168570+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/logging/handlers.py", line 930, in emit 2016-12-30T22:23:13.168571+00:00 app[web.1]: smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) 2016-12-30T22:23:13.168573+00:00 app[web.1]: (code, msg) = self.connect(host, port) 2016-12-30T22:23:13.168572+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 256, in __init__ 2016-12-30T22:23:13.168573+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 317, in connect 2016-12-30T22:23:13.168574+00:00 app[web.1]: (code, msg) = self.getreply() 2016-12-30T22:23:13.168575+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/smtplib.py", line 365, in getreply 2016-12-30T22:23:13.168576+00:00 app[web.1]: + str(e)) 2016-12-30T22:23:13.168577+00:00 app[web.1]: SMTPServerDisconnected: Connection unexpectedly closed: timed out 2016-12-30T22:23:13.168577+00:00 app[web.1]: Logged from file app.py, line 1423 2016-12-30T22:23:13.168578+00:00 app[web.1]: Exception on /favicon.ico [GET] 2016-12-30T22:23:13.168579+00:00 app[web.1]: Traceback (most recent call last): 2016-12-30T22:23:13.168580+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app 2016-12-30T22:23:13.168580+00:00 app[web.1]: response = self.full_dispatch_request() 2016-12-30T22:23:13.168582+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request 2016-12-30T22:23:13.168582+00:00 app[web.1]: rv = self.handle_user_exception(e) 2016-12-30T22:23:13.168583+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception 2016-12-30T22:23:13.168584+00:00 app[web.1]: reraise(exc_type, exc_value, tb) 2016-12-30T22:23:13.168585+00:00 app[web.1]: rv = self.preprocess_request() 2016-12-30T22:23:13.168584+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1473, in full_dispatch_request 2016-12-30T22:23:13.168586+00:00 app[web.1]: rv = func() 2016-12-30T22:23:13.168587+00:00 app[web.1]: File "/app/app/views.py", line 32, in before_request 2016-12-30T22:23:13.168586+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/flask/app.py", line 1666, in preprocess_request 2016-12-30T22:23:13.168588+00:00 app[web.1]: db.session.add(g.user) 2016-12-30T22:23:13.168589+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 150, in do 2016-12-30T22:23:13.168589+00:00 app[web.1]: return getattr(self.registry(), name)(*args, kwargs) 2016-12-30T22:23:13.168590+00:00 app[web.1]: File "/app/.heroku/python/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1490, in add 2016-12-30T22:23:13.168591+00:00 app[web.1]: raise exc.UnmappedInstanceError(instance) 2016-12-30T22:23:13.168591+00:00 app[web.1]: UnmappedInstanceError: Class 'werkzeug.local.LocalProxy' is not mapped

    I hope this is the last time I need to disturb you with this, but as I followed the entire tutorial (and it took me several days...) I would like to see the final results uploaded somewhere :D

    Manuel Ramos Calderón

  • #118 Miguel Grinberg said 2016-12-31T01:44:45Z

    @Manuel: this error is in your application. In file views.py, line 32 you are adding "g.user" to a database session. SQLAlchemy complains that this "g.user" object is a LocalProxy instance, not a model. Somewhere above that line, you are making a wrong assignments to g.user. If you are using Flask-Login and have something like "g.user = current_user", then change it to "g.user = current_user._get_current_object()" and hopefully that'll work for you.

  • #119 Bahrom said 2017-02-02T05:30:20Z

    "heroku addons:add heroku-postgresql:dev" is now "heroku addons:add heroku-postgresql:hobby-dev"

  • #120 Renner said 2017-02-26T04:27:20Z

    On "Deploying the Application", shouldn't you use "git push heroku version-0.18", instead of "git push heroku master"?

  • #121 Miguel Grinberg said 2017-02-26T06:24:20Z

    @Renner: Yes, thanks. I corrected the text.

  • #122 Revenn said 2017-03-18T20:37:04Z

    Hello Miguel, Great tutorial! Thank you so much for this awesome piece of knowledge. Btw. this heroku not working like it should. I have Mac OS, I did almost everything but when I went to step: $ heroku run init By terminal app said something like: Running init on ⬢ app_testing... up, run.3533 (Free) bash: init: command not found And stop executing this... What's going on?

  • #123 Miguel Grinberg said 2017-03-19T07:30:21Z

    @Revenn: it's possible that Heroku has made changes to the run command. Try issuing "heroku run python db_create.py". Basically I have replaced "init" with the actual command that this task is supposed to run.

  • #124 admirito said 2017-04-28T09:06:27Z

    @Revenn, @Miguel: heroku has not changed the run command. The problem is that it only deploy the "master" branch and ignore all the other branches. So, you have to push the repo with this command: "git push heroku version-0.18:master". And one another problem is in the Procfile: "web: gunicorn runp-heroku:app" but the app directory name is "app" so I have modified the Procfile: "web: gunicorn app:app".

  • #125 Shang Nan said 2017-06-29T09:25:19Z

    Hello, I'm so glad I find your tutorial as my first flask practice. And I have a problem in the final step to deploy the app on heroku. I use python3, and I used the guess_language package from https://bitbucket.org/spirit/guess_language/downloads/geuss_language-spirit-0.5a4.tar.bz2 and I want to know how can I specify this package in my requirements.txt file so I can seccessfully push it to heroku.

Leave a Comment