Table of Content

Objective

  • Flask web app:
    Hello World!
    count visit from web URL using Redis
  • using Dockerfile to auto build docker image, then start container and run flask web app on container
  • share image via Docker Hub

1) create docker machine for test

launch msys shell from portabledevops, a portable lightweight devops tool on windows.

$ dm create -d "virtualbox" --virtualbox-hostonly-cidr "10.10.100.1/24" testdocker
$ denv testdocker
$ dm ls
NAME         ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
testdocker   *        virtualbox   Running   tcp://10.10.100.100:2376            v17.06.2-ce

2) auto build image by Dockerfile

create work folder flaskhello/, create Dockerfile, app.py and requirements.txt under it.
sample script here are from Docker official get-started guide

Dockerfile

# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

app.py

app.py is main loop for Flask web,

from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
    try:
        visits = redis.incr("counter")
    except RedisError:
        visits = "<i>cannot connect to Redis, counter disabled</i>"

    html = "<h3>Hello {name}!</h3>" \
           "<b>Hostname:</b> {hostname}" \
           "<b>Visits:</b> {visits}"
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

os.getenv, socket.gethostname will get info from container, not docker host, hostname is container id in fact.

requirements.txt

this is list of python lib to be installed.

Flask
Redis

So this demo only install python lib for Redis, not install Redis database itself, it will get exception when connect to Redis in web url.

3) build flaskhello app

$ docker build -t flaskhello .
Sending build context to Docker daemon  4.608kB
Step 1/7 : FROM python:2.7-slim
2.7-slim: Pulling from library/python
aa18ad1a0d33: Pull complete
93d43f82f795: Pull complete
c5b84867da0c: Pull complete
f3a027f2a2da: Pull complete
Digest: sha256:4e1518d4e30d93f3f197195fa3b4a0bcdc185a4501b7c109344cbbd569d1b40e
Status: Downloaded newer image for python:2.7-slim
 ---> 8b88f06b72d7
Step 2/7 : WORKDIR /app
 ---> b0b179e84988
Removing intermediate container a1ca7c19bc3c
Step 3/7 : ADD . /app
 ---> e73279011bfc
Removing intermediate container a2f53e979dee
Step 4/7 : RUN pip install -r requirements.txt
 ---> Running in d4dd1b9b09c2
Collecting Flask (from -r requirements.txt (line 1))
  Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
Collecting Redis (from -r requirements.txt (line 2))
  Downloading redis-2.10.6-py2.py3-none-any.whl (64kB)
Collecting itsdangerous>=0.21 (from Flask->-r requirements.txt (line 1))
  Downloading itsdangerous-0.24.tar.gz (46kB)
Collecting Jinja2>=2.4 (from Flask->-r requirements.txt (line 1))
  Downloading Jinja2-2.9.6-py2.py3-none-any.whl (340kB)
Collecting Werkzeug>=0.7 (from Flask->-r requirements.txt (line 1))
  Downloading Werkzeug-0.12.2-py2.py3-none-any.whl (312kB)
Collecting click>=2.0 (from Flask->-r requirements.txt (line 1))
  Downloading click-6.7-py2.py3-none-any.whl (71kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->Flask->-r requirements.txt (line 1))
  Downloading MarkupSafe-1.0.tar.gz
Building wheels for collected packages: itsdangerous, MarkupSafe
  Running setup.py bdist_wheel for itsdangerous: started
  Running setup.py bdist_wheel for itsdangerous: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a
  Running setup.py bdist_wheel for MarkupSafe: started
  Running setup.py bdist_wheel for MarkupSafe: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/88/a7/30/e39a54a87bcbe25308fa3ca64e8ddc75d9b3e5afa21ee32d57
Successfully built itsdangerous MarkupSafe
Installing collected packages: itsdangerous, MarkupSafe, Jinja2, Werkzeug, click, Flask, Redis
Successfully installed Flask-0.12.2 Jinja2-2.9.6 MarkupSafe-1.0 Redis-2.10.6 Werkzeug-0.12.2 click-6.7 itsdangerous-0.24
 ---> 520c80b0d4ee
Removing intermediate container d4dd1b9b09c2
Step 5/7 : EXPOSE 80
 ---> Running in 400eb60a6273
 ---> 2910a96f4894
Removing intermediate container 400eb60a6273
Step 6/7 : ENV NAME World
 ---> Running in 85109c944baf
 ---> ef4433dc4c69
Removing intermediate container 85109c944baf
Step 7/7 : CMD python app.py
 ---> Running in 43aed160271a
 ---> a226dc706f6a
Removing intermediate container 43aed160271a
Successfully built a226dc706f6a
Successfully tagged flaskhello:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

4) run app

check app image just built,

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flaskhello latest a226dc706f6a 5 mins ago 195MB
python 2.7-slim 8b88f06b72d7 15 mins ago 184MB

run flaskhello app in container, mapping localhost:4000 to container port 80,

docker run -it -p 4000:80 flaskhello
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
10.10.100.1 - - [24/Sep/2017 20:22:32] "GET / HTTP/1.1" 200 -

verify web app running by

http://10.10.100.100:4000/

Hello World!

Hostname: 828a15523c37
Visits: cannot connect to Redis, counter disabled

also can verify hostname inside docker vm host,

docker@testdocker:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
828a15523c37 a226dc706f6a "python app.py" 56 seconds ago Up 56 seconds 0.0.0.0:4000->80/tcp mystifying_hopper

5) share app image

login to docker hub

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: dreamcloud
Password:
Login Succeeded

tag image

Before we upload image to Docker Hub registry, need to tag the image in format: username/repository:tag

in our case,

$ docker tag flaskhello dreamcloud/flaskhello:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dreamcloud/flaskhello latest a226dc706f6a 30 mins ago 195MB
python 2.7-slim 8b88f06b72d7 40 mins ago 184MB

push image

$ docker push dreamcloud/flaskhello:latest
The push refers to a repository [docker.io/dreamcloud/flaskhello]
26e2e235a7ef: Pushed
bceb4698c3a9: Pushed
ed658958efb4: Pushed
24b02a08f57d: Mounted from library/python
aed9311ebf15: Mounted from library/python
17f9d9d4ce37: Mounted from library/python
18f9b4e2e1bc: Mounted from library/python
part2: digest: sha256:e1588720beb9e6acef48f8014166f7cef0a1a59e7527e555b62e1648f4d47972 size: 1787

as test, you can delete image from local, then pull or run uploaded image from Docker Hub.

$ docker rmi a226dc706f6a
$ docker run -it -p 4000:80 dreamcloud/flaskhello