DEV Community

Robert Rees
Robert Rees

Posted on • Updated on

Deploying a basic Python Flask app to Fly

Due to Heroku shutting down their free services I need to find a new home for a number of apps that I have that are useful but which I use only occasionally and don't wish to assign a constant dyno to.

There are a number of contenders for alternative hosting so I decided to start with Fly.

I like to try and understand the process so I don't really like a lot of the tutorials that are "clone this repo, run this command, you're done". Since I need to transfer an existing app I need to understand a bit more of how the whole thing works.

For Fly though the Python tutorial is out of date so even if you follow it step by step then it wouldn't work right out of the box.

Fly's Python solution is quite a long way away from the Heroku push to deploy. The deployment CLI is not complicated but you need to understand a level of detail beyond the Python application to get things working.

It is compatible with Heroku buildpacks but for me that has been an invisible layer so far in my use of PaaS but I was able to identify and deploy an appropriate container to run the basic application code but I'm still not quite sure whether it's quite right.

While Fly has a configuration file the container management pokes out of the developer abstraction and you need to understand a bit about the port routing to make things work.

For Python apps the recommendation is to use Gunicorn to proxy between the application ingress port and the Flask application. That's fair enough I've done a bit of Gunicorn in the past and it's lucky I did as the none of the default ports and host binding for Flask, Gunicorn and Fly work together out of the box.

While I had a bit of a convoluted solution I think in hindsight the simplest thing is to make Gunicorn the glue and have it bind to and accept connections on port 8080.

Deployment involves a lot of staring at Docker layers (just like work!) and when I was having configuration problems it felt like a long build time to just adjust the parameters of a config file.

Overall the developer experience is much less friendly than Heroku so far but the runtime does to be suitable for my needs in terms of sharing the free CPU allowance across all your apps.

Update (17th October 2022)

So after a bit of a negative experience I was able to refine my initial deployment a bit. The developer experience overall remains quite bad but reading the community help was useful in understanding Fly's expectations of what an app deployment will involve.

As a concrete example during the gap between my initial deployment and returning to the code there was a new version of the CLI tool. What practically I experienced was that when I came to re-deploy my app with a one-line change, it didn't work. Cue much scratching of heads and poking at the CLI.

Pasting my error message into the help forums resulted in people having very similar problems but which were resolved months ago.

I updated the CLI but the problem remained. I then used the flyctl doctor command which correctly diagnosed that I was unable to connect to the build server and suggested a command to reset the Wireguard client. This reconnected me but illustrates part of the experience problem.

Fly gives you a remote Docker builder (which I think is a very sensible decision) but the connection is via a local Wireguard installation (not a technology I'm familiar with). Connecting a failing deployment to a connection failure with your builder instance to the secret Wireguard client that is running is virtually impossible. If you know about the debug command in the CLI then it runs through the steps that I was poking at manually and ineffectively. But this command is not mentioned in the tutorials as a troubleshooting method. Documentation is clearly hard.

With my builds and deployments up and running I set about reorganising my application from the overly simplistic single file version to a more conventional directory structure.

At which point my app just died on deployment with no helpful error messages.

I'm still not quite sure what I did wrong but the gunicorn command was no longer visible.

Other people had had a similar error message and the advice that came through all seemed to be along the lines of "don't use buildpacks, use your own Dockerfile".

Now from an unpaid support point of view that's a very valid position because it's a lot easier to help someone if they just have a single file to debug but if this is the recommended situation then again I think the introductory documentation could be changed to reflect this.

Using Docker

So I have a reasonable amount of Docker experience but I was a bit lost as to how to get one working for the Fly setup. The base image was straight-forward but as I was using pipenv for my dependencies (as that is how my Heroku projects work) I had to cadge from a few people's files to get something working.

Even then I had quite a bit of voodoo and was able (once I had something that actually booted) to cut it down quite a lot. This is the working Fly Python Dockerfile I ended up with.

I think a lot of my issues were probably with the Docker syntax for the command line but no error was shown. The instance just never became healthy so I was just kind of guessing what was going on.

I also think that while I had things working locally I probably needed somehow to test my Dockerized application locally but that seemed to defeat the point of a Heroku-like PaaS to me. I want to combine a configuration file with a working local application and get it deployed in the cloud.

To some extent though I've come to terms with the fact that Fly isn't Heroku and there is a way of working with it that is optimal and that workflow is still evolving.


One nice thing I noticed about the Fly deployment is that it seems to handle HTTP to HTTPS automatically, something I was using Flask-SSLify for on Heroku.

Update (20th April 2023)

I've migrated all my Heroku apps to Fly now and most of them now use buildpacks for a much more Heroku-like experience.

Top comments (0)