Deploying a Flask App to AWS Lambda
Tags: aws, python, serverless
Published: 2020-07-10 17:56:25

_In which I compare deploying Flask Apps to AWS Lambda with Zappa and Serverless Framework_

{% github divporter/flask-lambda no-readme %}

One of the first programming languages I learned was Python.  The first version (v0) of my hobby site [VeganFeeds](https://veganfeeds.com.au) however was written in PHP with an Apache server connected to a PostgreSQL database serving up dynamically rendered HTML. In the second version, confusingly named v1, I moved from the traditional LAMP stack (or LAPP in my case) to the [JAM stack](https://jamstack.org/). Being familiar with Python, I decided that [Flask](https://flask.palletsprojects.com/en/1.1.x/) would be the 'A' in my JAM stack.

After having built the Flask app, instead of continuing with a traditional web server I decided to give the serverless approach a try. Now that I've finished my trip down memory lane, let's skip ahead to deploying a Flask app to [AWS Lambda](https://aws.amazon.com/lambda/).  

While [SAM CLI](https://aws.amazon.com/serverless/sam/) can deploy Python Lambdas, you can't deploy a Flask app straight out of the box. Although you can with a little help from [flask-serverless](https://github.com/revmischa/flask-serverless). I have not used it personally so I won't review it here.

## **Zappa**
Given an existing Flask app, [Zappa](https://github.com/Miserlou/Zappa) is the easiest way to get your Flask app up there. And when I first discovered Zappa I knew next to nothing about the AWS ecosystem and so it was a friendly resource to enable me to get my Flask app out into the wild.

It is as simple as:
```
$ pip install zappa
$ zappa init
$ zappa deploy
```

Unfortunately though it's never that easy. The above assumes that you have installed your dependencies in a virtual environment within the project directory.  So if you haven't done that you will get an error like below: 
```
Error: Zappa requires an active virtual environment!
```   
So let's remedy that by setting up a virtual environment and installing our dependencies into it.
```
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install zappa flask 
```  
OK, so now we're ready to deploy. If we run `zappa deploy` again we now face a new error.
```
Error: This application is already deployed - did you mean to call update?
```
As featured as Zappa is, I find having separate commands for an initial deploy and updating an existing deployments annoying.  Anyway, let's do it.  Let's call `zappa update`.  And hooray it works.
```
Your updated Zappa deployment is live!: https://051zc5ynck.execute-api.ap-southeast-2.amazonaws.com/dev
```
Let's have a look in the browser.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mejpw1wkjur2ak46wq33.png)
Yep! Looks good.

Chances are though, if you're building an app beyond "hello world" you may end up adding a dependency that works fine on your development OS but is incompatible in the Lambda environment. Then you're going to need to mimic the Lambda environment when installing your dependencies with docker using the very handy [Lambda Docker images](https://hub.docker.com/r/lambci/lambda/) built by the lambci team.  So you could do something like:
```
$ pip freeze > requirements.txt
$ docker run -ti -v $(pwd):/var/task  --rm lambci/lambda:build-python3.6 bash -c "python3 -m venv zappa_venv && source zappa_venv/bin/activate && pip install -r requirements.txt"
```
Now the dependencies are built in the correct environment and when you run `zappa deploy` your Lambda should now work again. But that's a great big ugly command. Sure you use a Dockerfile, but if you're unfamiliar with Docker then that's going to make things harder. 

## **Serverless Framework**
Now despite all that Zappa bashing it was around when there was little else and really stretched the capabilities of CloudFormation and did a lot of heavy lifting behind the scenes before native support was rolled out as standard.  It also comes with a lot of handy features such as the `keep_warm` feature to get around the cold start issue.

The [Serverless Framework](https://www.serverless.com/) is a more generic tool.  Not only covering languages other than Python, such as Node.js and Ruby, but also other Function-as-a-Service (FaaS) providers such as Google Cloud and Microsoft Azure.

Installation-wise the Serverless Framework is not as friendly to set up the first time as Zappa. Obviously aside from a Python installation you will also need [Node.js](https://nodejs.org/) installed as a prerequisite.

Assuming you have node next is to install serverless via npm which ships with Node.js
```
$ npm install -g serverless
```
The `-g` flag means it is now available globally and you need not install it again for future projects.  OK, so now what? 
```
$ serverless create --template aws-python3
```
OK cool, so we have a few files now.  `handler.py`,`.gitignore` and `serverless.yml` Focusing on `handler.py`, this file reveals the bare bones of a Python Lambda handler. So how do we squeeze our Flask app into this? Well we're actually not going to use it at all. We're going to use a plugin.  The Serverless community is very active with a slew of plugins to keep things nice and simple. The [serverless-wsgi](https://www.serverless.com/plugins/serverless-wsgi) plugin like Zappa is designed for deploying Python WSGI apps to Lambda and abstracting away the handler protocols.  You'll notice at the bottom of the link that the plugin draws inspiration from Zappa. To add the plugin you'll need to run
```
$ sls plugin install -n serverless-wsgi
```
Note that `sls` is a handy alias for `serverless`.
Next we need to add a few lines to the bottom of our `serverless.yml` file.
```
custom:
  wsgi:
    app: app.app
```
Next we need to modify our `serverless.yml` in the functions section so that it looks like this
```
functions:
  hello:
    handler: handler.hello
  api:
    handler: wsgi_handler.handler
    events:
      - http: ANY /
      - http: ANY {proxy+}
```
Unlike Zappa there is no dependency on running a virtual environment, however `serverless-wsgi` relies on the `virtualenv` module. In case you don't have it installed simply run 
```
$ pip install virtualenv
```
followed by
```
$ sls deploy
```
With any luck the output will now show the url for our newly created api
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jd0fz4us2icms4c5yjzt.png)

And if we open in a browser...

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c90ovzysajno82eghyaf.png)

It works great.  Now what if we want to add a second exclamation and say 'Hello, World!!'.  Unlike Zappa we can just run `sls deploy` again. The `deploy` command handles both creation and updates. Lovely!

OK, but what about my dependencies that I need docker for? Well thankfully there's a plugin for that. [serverless-python-requirements](https://www.serverless.com/plugins/serverless-python-requirements) will do the Docker/lambci stuff for you. It does other nifty stuff too, like zipping them up to reduce their size or even throwing them in a layer for you!

## **What about Chalice?**
[Chalice](https://aws.github.io/chalice/) is the offical AWS tool for deploying Python Lambdas.  It has a similar syntax to Flask (and other Python WSGI frameworks), so for Flask users it will feel comfortable.  But unlike Zappa or Serverless you can't use Chalice to deploy an existing Flask app.  You must refactor it first.  

Chalice is not as configurable as Zappa and Serverless, so you will not get to enjoy all the features of a Flask App along with the flexibility of Zappa or Serverless.  For starting a simple project Chalice will help you get up and running quickly. Then if you find it doesn't meet your needs then you can look to Zappa and Serverless.