Intervention Ninja - build flask app docker image (#02)

November 18, 2018

In the previous post, I’ve shared what we’re going to build and why. Today, we’re gonna finally do some real work - we’ll build flask application and we’ll run it in docker.

Let’s get started

I’ll start with building a docker image for our flask application. Let’s write a Dockerfile for it. I expect you to have some experience with docker, if you don’t - I recommend you to check this website.

1
2
3
4
5
6
7
8
9
FROM library/python:alpine3.7

RUN pip install Flask==0.12.2

ADD ./src /opt/intervention-ninja
WORKDIR /opt/intervention-ninja

EXPOSE 80
CMD ["python", "app.py"]

Nothing really fancy here, we’re using alpine version of Linux with python 3 (line 1). We’re installing flask on it using pip (line 3) and copying source files from src directory (line 5). Line 6 is setting work directory to /opt/intervention-ninja - that’s where we’ve just copied source code of our application to. Then we can run python app.py from that directory (line 9).

The main file for our flask application is src/app.py - I’ll put only the most important pieces here and the rest will be available at the end of this post.

1
2
3
4
5
6
7
8
@app.route("/")
def home():
    return render_template("index.html")

@app.route("/send", methods=["POST"])
def send_email():
    logging.info("Pretending to sent email...")
    return render_template("sent.html")

So far our application have two endpoints - the root endpoint renders Jinja2 template file index.html. The /send endpoint accepts POST request, sends an email (not really) and then renders Jinja2 template file sent.html.

1
2
3
4
5
6
7
8
9
@app.route("/_health")
def health_check():
    return make_response(
        jsonify({
            "version": os.environ.get("VERSION", "N/A"),
            "status": "AVAILABLE"
        }),
        200
    )

The last section defines _health endpoint, which has one purpose - it should tell us (or to the load balancer) whether this container can be shown to our customers or not. In real life, this endpoint should return status based on the current state of the app, checking also it’s dependencies etc.

It’s also a good practice to return version of the application from here, as it’s very useful for debugging.

I won’t show the rest of the files here as they’re not really interesting - it’s just a static content and bootstrap template. Feel free to check all of them in the branch flask-ec2. If you’re not familiar with Jinja2 templates or macros, check the docs first.

The full structure of our project now looks like this:

Intervention Ninja flask app project structure

Great! Now let’s build a docker image - run following command from your terminal.

docker build -t intervention-ninja-flask .

Docker daemon should execute all the steps from Dockerfile and last two lines in your terminal should look similar to this (the image id will be different):

Successfully built 4549d6651033
Successfully tagged intervention-ninja-flask:latest

Running the service

We’ll run our new docker image, expose it on our host (localhost) on port 5000 and we’ll run it as daemon (so we can still use our terminal).

docker run -p 80:80 -d intervention-ninja-flask

Now let’s check whether our docker container is running:

docker ps

should give you a following input

CONTAINER ID        IMAGE                      COMMAND             CREATED             STATUS              PORTS                NAMES
9b23694bf916        intervention-ninja-flask   "python app.py"     2 minutes ago       Up 2 minutes        0.0.0.0:80->80/tcp   quizzical_agnesi

That looks promising. Now open your browser on url http://localhost and you should see:

Intervention Ninja Flask - homepage

Let’s put some email address into the form and let’s click on the Send button. You should now be on the second page:

Intervention Ninja Flask - homepage

Ok, that was fun! Of course we’re not sending real emails - to be honest - i’ve removed this part from the code for the purpose of this blog post. Next task will be to ship this application to AWS ECS. Stay tuned!