Deploy Flask Apps to AWS Elastic Beanstalk using Travis CI

Setting up a CI/CD pipeline for your Flask application with Travis CI and AWS Elastic Beanstalk

Deploy Flask Apps to AWS Elastic Beanstalk using Travis CI - Post illustration

In this article, we are going to deploy a Flask application to AWS Elastic Beanstalk via a GitHub and Travis CI deployment pipeline. Our goal: After pushing changes to your code to GitHub, Travis CI should pull our code, perform all tests, and if they pass, deploy the application to AWS Elastic Beanstalk. Let's go!

Dependencies and Project Setup

First, we need to install all dependencies on our local machine. Because AWS Elastic Beanstalk requires context about which environment our application needs to run in, we will create a Python virtual environment and install all dependencies within the virtual environment. Run the following commands in your terminal to get started and set up the project directory:

Terminal
export PROJECT_NAME=deploy-eb-via-travis-ci
mkdir ~/VSCodeProjects/$PROJECT_NAME
cd ~/VSCodeProjects/$PROJECT_NAME
# create virtual environment and activate it
python3 -m venv my_venv
source my_venv/bin/activate
# within virtual environment, install the following:
python3 -m pip install flask
python3 -m pip freeze > requirements.txt
deactivate
# set up file structure
touch app.py wsgi.py .travis.yml
mkdir tests
touch tests/test_42.py

Then, open your project directory with your favorite code editor. You can use your command line: For example for PyCharm you can just type charm . or for VSCode you can type code . Learn more about how to set up command line interfaces for PyCharm here or VSCode here.

A Simple Flask Application

We need to create a basic "Hello World" Flask application, which we can then use for the purposes of setting up our continuous deployment pipeline scenario. Assume that this is a placeholder before you are able to deploy your (real) Flask application. The deployment of a real-world application likely follows very similar steps as described here. As seen above, we created four empty files.

Add the following code to set up two routes for / and /foo:

app.py
Python
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def index():
    return jsonify({"hello": "world", "from": "index"})

@app.route("/foo")
def foo():
    return jsonify({"hello": "world", "from": "foo"})

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

Configure WSGI:

wsgi.py
Python
from app import app as application

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

Add a mock test (which always passes):

tests/test_42.py
Python
def test_42():
    assert 42 == 42

We will revisit the fourth required file when we are configuring Travis CI. As for now, we are ready to (manually) deploy our first version to AWS Elastic Beanstalk.

Deployment to AWS Elastic Beanstalk

Before we can deploy our application, make sure that the AWS Elastic Beanstalk CLI eb is installed. If it's not installed on your local machine, you can follow the instructions provided by AWS. You can check whether your local machine has eb installed by running the following command:

Terminal
eb --version

The following commands will set up AWS Elastic Beanstalk with an empty application. Make sure to provide your AWS region (in my case eu-west-1) and required Python version (in my case python-3.6 is the latest Python version supported by EB at the time of writing) accordingly:

Terminal
export REGION=eu-west-1
export PYTHON_VERSION=python-3.6 # latest version supported at time of writing
eb init -p $PYTHON_VERSION -r $REGION $PROJECT_NAME

Next, we need to configure the default WSGI path. This will tell AWS Elastic Beanstalk where the entry point of our Flask application is located at. In our case, it's the file wsgi.py. Run the following commands in your terminal:

Terminal
# default WSGI path is application.py
# alternative way to configure this:
# `eb config` and then set manually
mkdir .ebextensions
echo "option_settings:
  - namespace: aws:elasticbeanstalk:container:python
    option_name: WSGIPath
    value: wsgi.py" > .ebextensions/wsgi.config

Our continuous deployment pipeline will be git-based, since we push our changes to GitHub. Initialize git with the following commands:

Terminal
git init
git add .
git commit -m "First commit"

Note: AWS Elastic Beanstalk will create .zip archives for application deployment based on your changes that are committed to git. Therefore, remember to commit your changes before manually deploying via eb deploy, otherwise your changes will not be deployed!

In a real-world context, you would have more than one environment for your application, the most basic scenario being a live deployment (where users are interacting with your app) and a test deployment (where you will deploy new releases to test them). You can create an environment on AWS Elastic Beanstalk with the following commands:

Terminal
export ENVIRONMENT_NAME=test # for example: "test" or "live"
export INSTANCE_TYPE=t2.nano # specify the instance type
eb create $ENVIRONMENT_NAME-$PROJECT_NAME --single -i $INSTANCE_TYPE
eb use $ENVIRONMENT_NAME-$PROJECT_NAME

Now, you can trigger a manual deployment to AWS Elastic Beanstalk:

Terminal
eb deploy

If you can confirm your Flask application is working as expected, we can move on and integrate Travis CI to implement our continuous deployment pipeline.

Travis CI Pipeline Configuration

Before we can proceed, you need to push your changes to GitHub:

Terminal
git remote add origin https://github.com/owner/repo.git
git push -u origin master

Make sure to enable Travis CI in the repository settings, either in your GitHub account or your Travis CI account. This will make Travis CI listen to changes of your repository automatically trigger a build for this repository whenever a push is registered. Your Travis CI account needs to have the required permissions to access your GitHub account and your repositories.

Make sure the Travis command line interface travis is installed:

Terminal
travis --version

If it's not installed, you can run the following command in order to install it:

Terminal
gem install travis

There are two versions of Travis CI: A free version at travis-ci.org and a paid version travis-ci.com. If you are using the paid version instead of the open source version, you need to login first, and use the argument --pro with every command. In this article, we will use travis-ci.com. Ensure you are logged in to travis-ci.com by running:

Terminal
travis whoami --pro

If your username shows up, you're all set. Otherwise, sign in from your terminal using:

Terminal
travis login --pro

The repository is automatically inferred from your git remote (which is GitHub). To tell Travis CI more about our application, what needs to be tested, and where our application should be deployed to, add the following configuration for Travis CI:

.travis.yml
YAML
language: python
python:
  - 3.6
before_install:
  - python --version
  - pip install -U pip
  - pip install -U pytest pytest-cov
  - pip install codecov
script: pytest
after_success:
  - pytest --cov=./
  - codecov
deploy:
  provider: elasticbeanstalk
  access_key_id:
    secure: $AWS_ACCESS_KEY_ID
  secret_access_key:
    secure: $AWS_ACCESS_KEY_SECRET
  region: $AWS_REGION
  app: $AWS_EB_APPLICATION
  env: $AWS_EB_ENVIRONMENT
  bucket_name: $AWS_EB_S3_BUCKET

Then add the necessary encrypted environment variables automatically to your .travis.yml file (see references above):

Terminal
travis encrypt --pro AWS_ACCESS_KEY_ID="YOUR_KEY_ID_HERE" --add
travis encrypt --pro AWS_ACCESS_KEY_SECRET="YOUR_KEY_SECRET_HERE" --add
travis encrypt --pro AWS_REGION=$REGION --add
travis encrypt --pro AWS_EB_APPLICATION=$PROJECT_NAME --add
travis encrypt --pro AWS_EB_ENVIRONMENT=$ENVIRONMENT_NAME-$PROJECT_NAME --add
travis encrypt --pro AWS_EB_S3_BUCKET="YOUR_BUCKET_HERE" --add # change this as needed – a new bucket will be automatically created if it does not yet exist

It's strongly recommended to encrypt environment variables with sensititve information, such as AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY_SECRET. Sensitive information should never end up in source control. Check out this resource for more information on how to encrypt environment variables for Travis CI. An alternative to the above step (travis encrypt ...) is to configure the environment variables using the Travis CI web application and adding them to the repository settings. An alternative to setting encrypted environment variables is to use the following command (not recommended for secrets):

Terminal
travis env set VARIABLE_NAME "value" # Not encrypted!

And... we're done! From now on, you can make changes to your application and simply push them to GitHub. Everything else is set up and done automatically for you: Travis CI will pull your code, perform the tests, and if they pass, deploy the application to AWS Elastic Beanstalk. Should the tests not pass, the application version will not be deployed, and you will be able to investigate further as to why.

Conclusion

You now have gone through the required steps to create a CI/CD pipeline for your Flask application. Newly pushed changes are automatically analyzed and tested by Travis CI. If all tests pass, new versions of your Flask application will be automatically deployed to the specified environment(s) within AWS Elastic Beanstalk.

I hope you enjoyed this article –  please let me know what you think, or if you have any questions.

© 2024 Camillo Visini
Imprint