In this post, we are going to look at how to deploy two Docker containers on AWS Elastic Beanstalk Applications.
Today, Docker containers are being used by many companies in sophisticated microservice infrastructures. From a developer point of view, one of the biggest benefits of Docker is its ability to package our code into reusable images. This assures us that our code will work as expected wherever it will be run. Development is also made easier and faster using Docker. Project dependencies are installed into isolated containers without the need to install them on our local machine. This allows us to develop applications with different requirements in a secure and isolated way.
Too often, developers are stuck trying to find an easy way to run their containers in production. With so many different technologies available, choosing among them isn’t an easy choice. Topics like high availability, scalability, fault tolerance, monitoring, and logging should always be part of a solid production environment. However, without enough knowledge, it may be difficult to achieve them using containerized applications.
AWS Elastic Beanstalk is a service for quickly deploying and scaling applications in the Amazon cloud. This includes services developed with Java, .NET, PHP, Python, Ruby, and Docker. Its support for Docker containers makes it an excellent choice for deploying Dockerized applications into solid production environments that are easy to manage and update.
Today, we’ll use a very practical example to show how easy it is to deploy Dockerized applications. The code we will use is available in this Github repo. Feel free to clone it locally to follow along with us.
Our scenario is a basic web application with a single “Hello World” API endpoint served by a proxy server. We are going to implement it with two containers. The first container runs a Flask application with uWSGI, and the second container runs Nginx as a reverse proxy.
We want to start by declaring our project dependencies in a file called requirements.txt:
Flask==0.12 uWSGI==2.0.14
Our desired application behavior can be easily implemented with Flask creating a file called main.py with this content:
from flask import Flask, jsonify app = Flask(__name__) @app.route('/') def index(): return jsonify('Hello World!') if __name__ == '__main__': app.run(port=9000)
To locally spin up our application with uWSGI, we can execute:
uwsgi --socket 0.0.0.0:9000 --protocol http -w main:app
Our application is ready. Now, we can define how to containerize it by defining the following Dockerfile:
FROM python:2.7 EXPOSE 9000 COPY requirements.txt / COPY main.py / RUN ["pip", "install", "-r", "requirements.txt"] CMD ["uwsgi", "--socket", "0.0.0.0:9000", "--protocol", "http", "-w", "main:app"]
To build it, simply execute the command “docker build -t server .“. Docker will build an image called “server” with our code. Once it is complete, we can start a container from it by executing the command “docker run -d -p 9000:9000 –name server server“. If we open a browser to http://127.0.0.1:9000/ we will see our “Hello World” page.
Next, we should add a second container that runs Nginx to use it as a reverse proxy to our web server container running uWSGI. Start creating this configuration file called default.conf inside another folder:
server { listen 80; location / { proxy_pass http://server:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
This can now be packed into an image with this simple Dockerfile placed in the same folder of default.conf:
FROM nginx:latest COPY default.conf /etc/nginx/conf.d/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
We can build the image for our proxy container by executing the command “docker build -t proxy”. Now, we are able to start a new container from our image by executing “docker run -d -p 8080:80 –link server:server –name proxy proxy“. If we open our browser to http://127.0.0.1:8080/ we will see that our request is proxied from the proxy container by running Nginx through the application running uWSGI.
Now that we have achieved our goal locally, it’s time to replicate the same situation in a production environment.
To start, we should store our images in a secure Docker repository. If we want a private and cost-effective repository within our AWS account, we can use AWS Elastic Container Registry (ECR). Otherwise, we can simply push our images to the docker hub. Using ECR is simple and fast, and we just need to log into the AWS console, select ECS, and then create two new repositories for our images. ECR will provide us with instructions for pushing our local images.
Before going into production, the last thing we need to do is to define a configuration file to inform Elastic Beanstalk about how to use our images. This can be done by creating a file called Dockerrun.aws.json:
{ "AWSEBDockerrunVersion": 2, "containerDefinitions": [ { "name": "server", "image": "lzac/eb-docker-server", "essential": true, "memory": 200, "cpu": 1 }, { "name": "proxy", "image": "lzac/eb-docker-proxy", "essential": true, "memory": 200, "cpu": 1, "portMappings": [ { "hostPort": 80, "containerPort": 80, "protocol": "tcp" } ], "links": [ "server:server" ] } ] }
As you can see, we are defining the same situation that we had locally: we need one container running the application server and another one running the proxy. They are linked together using the standard Docker linking pattern. For the purposes of this post, images are publicly stored on Docker Hub. Modify their URL if you are following the steps and you if pushed them to ECR or any different Docker registry.
Everything we need to run our containers in production is ready. From the AWS console, go to Elastic Beanstalk and start creating a new application. The only required information that we need to provide is the following:
Next, click on “Configure more options” and modify the “Capacity” section by selecting “Load Balanced” as environment type using from two to four instances in any availability zone.
Now, click “Create app.” Elastic Beanstalk will start provisioning every resource needed to run our code within the AWS cloud. It will create:
Through these services, Elastic Beanstalk is automatically configuring many of the features that should always be part of best practices in any production environment. Provisioned EC2 instances will have Docker pre-installed. Once initiated, they will pull both images from the repository and start the required containers linked as defined in our configuration file. Once deployed, we are able to see the correct output of our application by opening a browser to the public URL provided by AWS Elastic Beanstalk.
We can now make use of the powerful Elastic Beanstalk configuration panel to modify a variety of settings for our application in just a few minutes. Elastic Beanstalk will transparently apply them for us.
Let’s take a quick look at each section of the Elastic Beanstalk console to see what we can do:
To sum up, we’ve taken a look at how to deploy Docker containers on AWS Elastic Beanstalk applications. As you can see, AWS Elastic Beanstalk is very easy to use and it is an excellent solution for deploying Docker containers on the AWS cloud. Developers are not forced to learn any new technologies and deployments can be easily made without particular operations knowledge/experience.
It's Flash Sale time! Get 50% off your first year with Cloud Academy: all access to AWS, Azure, and Cloud…
In this blog post, we're going to answer some questions you might have about the new AWS Certified Data Engineer…
This is my 3rd and final post of this series ‘Navigating the Vocabulary of Gen AI’. If you would like…