AWS DevOps & Developer Productivity Blog
Dockerizing a Python Web App
A few weeks ago Elastic Beanstalk announced support for deploying and managing Docker containers in the AWS cloud. In this post we’ll walk through Dockerizing a simple signup form web app originally written for the Elastic Beanstalk Python environment.
About the Signup Form App
We built and blogged about this app a few months ago. There’s a four part video series and a post that drills into the nuts and bolts of using DynamoDB and SNS with the app. Today we’re going to build on that content and talk about how we take that application and make it work with Docker and Elastic Beanstalk. We’re going to knock this out in 4 phases.
The Source, for Reference
The source code for the original (i.e., non-Dockerized) Python application is available on GitHub (in the master branch) at https://github.com/awslabs/eb-py-flask-signup/tree/master. The Dockerized version is in the docker
branch at https://github.com/awslabs/eb-py-flask-signup/tree/docker.
And if you prefer code and diffs to prose and blog posts, you can check out the nifty GitHub compare view to look at the differences between those two branches. That’s at https://github.com/awslabs/eb-py-flask-signup/compare/master…docker. You can check out every file added and every line changed to Dockerize this sample.
Dockerization Phase 1: Add a Dockerfile
Let’s start by pulling down the source from GitHub:
$> git clone git@github.com:awslabs/eb-py-flask-signup.git $> cd eb-py-flask-signup $> git checkout master
Looking at the contents of the directory we see this is a simple Python web app that uses the Flask framework, Boto for interacting with DynamoDB and SNS, and a few other dependencies declared in requirements.txt
.
Simple enough, so we create a Dockerfile
that will build an image suitable for running this application. The Dockerfile
is placed in the directory with the rest of the app source (i.e., alongside requirements.txt
, application.py
, etc.):
FROM ubuntu:12.10 # Install Python Setuptools RUN apt-get install -y python-setuptools # Install pip RUN easy_install pip # Add and install Python modules ADD requirements.txt /src/requirements.txt RUN cd /src; pip install -r requirements.txt # Bundle app source ADD . /src # Expose EXPOSE 5000 # Run CMD ["python", "/src/application.py"]
Dockerization Phase 2: Test Locally
Although this app requires a DynamoDB table and SNS topic to be completely functional, we can test it without them:
First, build the Docker image:
$> docker build -t eb-py-sample .
Last (and straight to profit!), run a container from the image (mapping container port 5000 to host port 8080, and setting a few env vars discussed below):
$> docker run -d -e APP_CONFIG=application.config.example -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY -p 8080:5000 eb-py-sample
On OS X I can open http://localhost:8080
and there’s my app!
Sidebar: We used the
-e
options to pass in a few environment variables:
APP_CONFIG
: The app expects this environment variable to point to its configuration file. We point to the default config file bundled with the app. You could create a DynamoDB table and SNS topic and add them to this conf file to make the app work perfectly in your local dev env.AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
: The app uses Boto to connect to DynamoDB and SNS, and Boto uses these environment variables to sign requests to those services. This is for local dev only. When we deploy to Elastic Beanstalk we’ll use IAM Roles.
Dockerization Phase 3: Modify .ebextensions
Our app has a special .ebextensions
directory with a setup.config
file. We use the file to tell Elastic Beanstalk to create the DynamoDB table and SNS topic our app needs, and also to create an app config file – /var/app/app.config
– that includes the names of the DynamoDB table and SNS topic that were just created.
The file also specifies a few things that are specific to the Python (as opposed to Docker) environment type in Elastic Beanstalk. We need to remove those bits now:
Modify the files
member to remove the owner
and group
keys so it looks like:
files: "/var/app/app.config": mode: "000444" content: | AWS_REGION = '`{ "Ref" : "AWS::Region"}`' STARTUP_SIGNUP_TABLE = '`{ "Ref" : "StartupSignupsTable"}`' NEW_SIGNUP_TOPIC = '`{ "Ref" : "NewSignupTopic"}`'
Modify option_settings
to remove the static files mapping so it looks like:
option_settings: "aws:elasticbeanstalk:customoption": "AlarmEmail" : "nobody@amazon.com" "aws:elasticbeanstalk:application:environment": "APP_CONFIG": "/var/app/app.config" "FLASK_DEBUG": "false" "THEME": "flatly"
Check out the earlier post for more info on setup.config
, or look at the Dockerized setup.config
on GitHub
Dockerization Phase 4: Deploy to Elastic Beanstalk
I’ve built and tested my container locally, removed a few .ebextensions
that were specific to the Elastic Beanstalk Python environment, and now I’m ready to deploy it – with confidence!
I create a file named Dockerrun.aws.json
in the same place I created the Dockerfile
. This file will tell Elastic Beanstalk how to run the Docker container and it looks like this (see the sidebar below for more details on this file):
{ "AWSEBDockerrunVersion": "1", "Volumes": [ { "ContainerDirectory": "/var/app", "HostDirectory": "/var/app" } ], "Logging": "/var/eb_log" }
Sidebar about
Dockerrun.aws.json
:The
Volumes
member will map/var/app
on the EC2 Instance to/var/app
in the container. This let’s the app running in the Docker container access theapp.config
file created by.ebextensions/setup.config
TheLogging
member indicates to Elastic Beanstalk that our Dockerized app will write logs to/var/eb_log
in the container. Beanstalk will automatically pull logs from this directory whenever you clickSnapshot Logs
in the console, or if you enable automatic log rotation:
I’ll commit my changes and use git archive
to make a zip to deploy to Elastic Beanstalk (or you can use the zip
tool or Finder or Windows Explorer to achieve the same):
$> git add Docker* && git commit -am "Dockerized" $> git archive --format=zip HEAD > eb-py-flask-signup.zip
And then I deploy the zip via the Elastic Beanstalk Management Console
When my environment is green, I can, of course, access it and verify it works:
I’ll also snapshot the environment’s logs:
Because I added the Logging
member to the Dockerrun.aws.json
file earlier, logs in the /var/eb_log
dir on the container will be rotated to S3 and I can access them in the browser:
Coming Up
In the next post, we’ll use the eb
command line tool to depoy this Dockerized app directly from the command line, no browser or management console necessary!
Resources
- Dockerized sample app on GitHub – https://github.com/awslabs/eb-py-flask-signup/tree/docker
- Docker on Elastic Beanstalk documentation – http://docs.thinkwithwp.com/elasticbeanstalk/latest/dg/create_deploy_docker.html