Deploy a Flask Website to AWS Elastic Beanstalk

Create a Flask-powered website and deploy it to AWS Elastic Beanstalk including SSL via Let's Encrypt

Deploy a Flask Website to AWS Elastic Beanstalk - Post illustration

This post explains how to set up a Flask website serving as a starting point for building a simple website. I've compiled the necessary steps to successfully deploy the Website via AWS Elastic Beanstalk, including configuration of SSL certificates via Let's Encrypt.

Project source code available at...
visini/barebone-flask-website

We'll use...

  • Python 3 and PyCharm as an IDE
  • AWS Elastic Beanstalk to host our website
  • Flask, a popular Python micro framework for the web
  • Flask’s automatically configured Jinja2-templating engine
  • Let’s Encrypt for SSL encryption
  • Gunicorn (WSGI server)

The end result is a fully working website serving as a starting point for your Flask-powered website project.

Project Setup and Dependencies

In this step we will install all requirements on our local machine. I’m using macOS 10.12 and Python 3.6 - the commands might not correspond exactly to your environment, so be sure to watch out for that.

Terminal
# create and go to project folder
cd ~/PycharmProjects/ # cd into your main project folder
mkdir barebone-flask-website
cd barebone-flask-website
# create virtual environment and activate it
python3 -m venv my_virtual_environment
source my_virtual_environment/bin/activate
# within virtual environment, install the following:
pip3 install flask
pip3 freeze > requirements.txt
deactivate
# set up file structure
touch app.py wsgi.py

Important: Be sure while debugging, when serving the website or when you install additional dependencies below using pip3, change to the virtual environment with the following commands:

Terminal
source my_virtual_environment/bin/activate # activate virtual environment
# your commands here
pip3 freeze > requirements.txt # after you're done, this will ensure that requirements.txt is up to date
deactivate # this will exit the virtual environment

Open the project folder in your code editor, e.g., VS Code or PyCharm.

To get started, add the following barebone code:

app.py
Python
from flask import Flask

application = Flask(__name__)

@application.route("/")
def hello():
    return "Hello World!"

if __name__ == '__main__':
    application.run(debug=True)

Prepare the WSGI server (we're using gunicorn):

wsgi.py
Python
from app import application

if __name__ == "__main__":
    application.run()

You can make sure that everything is working properly by directly executing the Flask app:

Terminal
# First option: Run with python interpreter (only for debugging)
python3 app.py --debug
# * Running on http://127.0.0.1:5000/
# * Restarting with stat
# * Debugger is active!

Alternatively, run gunicorn to serve your app:

Terminal
# Second option: Run with gunicorn (used for production later)
gunicorn --bind 0.0.0.0:8080 wsgi:application -w 1
# Starting gunicorn 19.7.1
# Listening at: http://0.0.0.0:8080 (19694)
# Using worker: sync
# Booting worker with pid: 19697

Press CTRL+C to exit the respective process. Our website is now working with a minimal “Hello world!” placeholder and we are ready to proceed.

Jinja2 Templating

We will set up a basic directory structure for your project to add templating capabilities to our app.

Terminal
mkdir templates static
touch templates/index.html templates/contact.html

Change your main Flask app code to accommodate the two example subpages (index page and contact page):

app.py
Python
from flask import Flask, render_template

application = Flask(__name__)

@application.route("/")
def index():
    return render_template('index.html', title='Index')

@application.route("/contact")
def contact():
    return render_template('contact.html', title='Contact')

if __name__ == '__main__':
    application.run(debug=True)

This structure will set up two views, called index and contact, with the according templates, index.html and contact.html which are stored in /templates.

You can use the below boilerplate HTML for these two template files, in order to get you started right away:

index.html
HTML
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
    <h1>{{title}}</h1>
    <p>This is the {{title}} page.</p>
  </body>
</html>

Deployment to AWS Elastic Beanstalk

In order to deploy our website to AWS Elastic Beanstalk, we first need to install the AWS Elastic Beanstalk command line interface. I recommend installation using brew, which handles all dependencies correctly. If you have it already installed on your machine, you can skip this step. Run this command in order to install the CLI:

Terminal
brew install awsebcli

You can check if the installation was successful by executing:

Terminal
eb --version

Now, run the following command in your project folder to initialize the application and register it with AWS Elastic Beanstalk (be sure to change the region accordingly; below, eu-west-1 is selected):

Terminal
eb init -p python-3.4 -r eu-west-1 barebone-flask-website

This will trigger several prompts which will help you to set up the application, most importantly the credentials for your AWS account.

  • You will need to enter your AWS credentials, namely aws-access-id and aws-secret-key, if not specified before via environment variables

The following command will create an environment (called testenv-barebone-flask-rest-website), provision the necessary instance (we’ll use a single instance setup without load balancer for now), and deploy your application:

Terminal
eb create testenv-barebone-flask-website --single -i t2.nano
eb use testenv-barebone-flask-website

This will take a few minutes to complete. Note that at this point, the application will not work yet, because the default WSGI-Path configured by AWS Elastic Beanstalk is application.py, whereas in our project it is wsgi.py.

In order to tell AWS Elastic Beanstalk to set up the EC2 instances with the correct WSGI-Path (where our website should ultimately be served from), we have to adjust the configuration:

Terminal
eb config

Change application.py to wsgi.py (see below). This will ensure that the correct file from our project folder is referenced (in other words, defining wsgi.py as the entry point to our application).

YAML
[...]
aws:elasticbeanstalk:container:python:
   NumProcesses: '1'
   NumThreads: '15'
   StaticFiles: /static/=static/
   WSGIPath: wsgi.py # change this from application.py to wsgi.py
[...]

Exit and confirm the changes. This will subsequently deploy the new configuration to your EC2 instance (i.e., replace the current instance).

After deployment of the new configuration (which takes another 2–3 minutes) you can type the following command to open the URL under which your website is served from - and confirm that everything is working as it should:

Terminal
eb open

You can also retrieve the CNAME (alongside other useful information about your environment) like so:

Terminal
eb status

Now, your website boilerplate is up and running on AWS Elastic Beanstalk. You can access it with the endpoint provided by the above command.

If you make changes to your app’s source code, you can re-deploy your app to Elastic Beanstalk via the following command:

Terminal
eb deploy

SSL Configuration via Let’s Encrypt

The steps below will show you how to configure an SSL certificate without additional charges, using Let’s Encrypt. First, you’ll need to create a hosted zone for your custom domain in Route 53. Then, you can attach a CNAME Alias in the hosted zone, pointing to your AWS Elastic Beanstalk deployment. See this AWS guide for more information.

Note: The next step will download a config file from gist.github.com. Be sure to check out the source code before pasting the command into your command line!

After you verify that the Route 53 DNS changes have propagated successfully, run the following commands in Terminal in the root of your project (be sure to replace the two environment variables with your email, as well as your domain name configured in Route 53 from the step above):

Terminal
eb setenv [email protected] LETSENCRYPT_DOMAIN=example.com
mkdir .ebextensions
wget -q https://gist.githubusercontent.com/visini/70c5b11c136be16ea2fe12a6d7b9bf3b/raw/231d3dedf4256b9081e46421714d7f8477055def/elasticbeanstalk-letsencrypt-ssl.config -O .ebextensions/elasticbeanstalk-letsencrypt-ssl.config
eb deploy

The config script above will install a cronjob to make sure that your SSL certificates are periodically renewed. Now, your website is served via AWS Elastic Beanstalk, accessible on your custom domain, and secured via SSL provided by Let’s Encrypt.

Project source code available at...
visini/barebone-flask-website

I hope you enjoyed this article  -  please let me know if you have any questions or if you run into any issue. Thanks for reading!

© 2024 Camillo Visini
Imprint