A simple article to explain how dockerized a Flask App.
In this article, I will show you how was my approach to solve this challenge!
Even though a simple "Hello World" I worked with the best practices of architecture to it. I had the following directory structure:
. ├── backend │ ├── app.py │ ├── blueprints │ │ ├── core │ │ │ ├── bp.py │ │ │ ├── __init__.py │ │ │ ├── routes.py │ │ │ └── tests │ │ │ └── test_bp_core.py │ │ └── __init__.py │ └── __init__.py ├── conftest.py ├── Dockerfile ├── .dockerignore ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── pytest.ini └── README.md
. (dot) is the root of project. In the
backend I have Flask application properly. I used the factory pattern for the architecture. So, in the
app.py I have this:
from flask import Flask def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = 'My_Top_Secert_Key' # Blueprint from backend.blueprints.core import bp as bp_core bp_core.config(app) return app
This code is responsible to create my Flask app. The focus of this article isn't to explain about Flask and so on. But, how to construct an app and run it into a Docker container. In the backend/bluprints/core I have my blueprint (see de documentation to know more about blueprints), the code is in bp.py and route.py, both configure my core blueprint. The first have this code:
from flask import Blueprint bp = Blueprint('core', __name__) def config(app): from backend.blueprints.core import routes # noqa app.register_blueprint(bp)
And the second has this code:
from backend.blueprints.core.bp import bp @bp.route('/') def home(): return "Hello World"
backend/blueprint/core/tests I have my tests of this blueprint. It's a simple test, and its code is:
import pytest from flask import url_for @pytest.fixture def resp(client): return client.get(url_for('core.home')) def test_bp_home_status_code_ok(resp): assert resp.status_code == 200
Again, the focus of this article isn't to explain detailed of the Flask application, so if you have questions about how to test Flask apps
Back to the root of the application, I have some files that are interesting to see!
One is the
conftest.py. This file is responsible for set up
pytest to run all tests in the application. Its code is:
import pytest from backend.app import create_app @pytest.fixture def app(): app = create_app() return app @pytest.fixture def client(app): with app.test_client() as c: yield c
Other is the
pytest.ini a simple file that inform to
pytest the pattern of the files tests names, in our case the
pytest.ini code is:
[pytest] python_files=test*.py *tests.py
Two other files are
Pipfile.lock. These files are created or updated by the command
pipenv install [package]. As we are going to see later, the most important in our case is the
Pipfile.lock because it locks the versions of all dependencies and allows a deterministic installation of these dependencies in our environment.
And we have
Dockerfile, too. This file is responsible for the Docker Image creation. This Image will be used to up the container with the Flask Application. Its code is:
FROM python:3.7.1 LABEL Author="Vicente Marçal" LABEL E-mail="[email protected]" LABEL version="0.0.1b" ENV PYTHONDONTWRITEBYTECODE 1 ENV FLASK_APP "backend/app.py" ENV FLASK_ENV "development" ENV FLASK_DEBUG True RUN mkdir /app WORKDIR /app COPY Pip* /app/ RUN pip install --upgrade pip && \ pip install pipenv && \ pipenv install --dev --system --deploy --ignore-pipfile ADD . /app EXPOSE 5000 CMD flask run --host=0.0.0.0
A brief explanaton about it:
LABEL Author="Vicente Marçal"
RUN mkdir /app
COPY Pip* /app/
RUN pip install --upgrade pip && \ pip install pipenv && \ pipenv install --dev --system --deploy --ignore-pipfile
ADD . /app
CMD flask run --host=0.0.0.0
Some words about the use of
Although ADD and COPY are functionally similar, generally speaking, COPY is preferred. That’s because it’s more transparent than ADD. COPY only supports the basic copying of local files into the container, while ADD has some features (like local-only tar extraction and remote URL support) that are not immediately obvious. Consequently, the best use for ADD is local tar file auto-extraction into the image, as in ADD rootfs.tar.xz /.
That's all we need to Dockerize our Flask App. Now we need to build and run our Docker image.
To build we use this Docker CLI command:
docker build -t vm_docker_flask .
docker build will build our image, with the flag
-t it put the tag vm_docker_flask in our image and, finally, the last part of the CLI is
. (dot) that inform to Docker that the Dockerfile is in the current directory.
This command shows us this result in our terminal:
Sending build context to Docker daemon 29.18kB Step 1/15 : FROM python:3.7.1 ---> 1e80caffd59e Step 2/15 : LABEL Author="Vicente Marçal" ---> Running in a7975e93672a Removing intermediate container a7975e93672a ---> 448662ef63d8 Step 3/15 : LABEL E-mail="[email protected]" ---> Running in 75f6319066de Removing intermediate container 75f6319066de ---> 0898192a03f7 Step 4/15 : LABEL version="0.0.1b" ---> Running in defb5aee2083 Removing intermediate container defb5aee2083 ---> 02042247ded0 Step 5/15 : ENV PYTHONDONTWRITEBYTECODE 1 ---> Running in 5e9124dfa05c Removing intermediate container 5e9124dfa05c ---> c35cd3ecc42f Step 6/15 : ENV FLASK_APP "backend/app.py" ---> Running in ea58e08644ad Removing intermediate container ea58e08644ad ---> f2fb780d29fc Step 7/15 : ENV FLASK_ENV "development" ---> Running in f72976410ba2 Removing intermediate container f72976410ba2 ---> bb444664e3b0 Step 8/15 : ENV FLASK_DEBUG True ---> Running in e18257443538 Removing intermediate container e18257443538 ---> a2bbca32f540 Step 9/15 : RUN mkdir /app ---> Running in ebbcc284fe40 Removing intermediate container ebbcc284fe40 ---> 7fb8c7fac9f8 Step 10/15 : WORKDIR /app ---> Running in c76604d10578 Removing intermediate container c76604d10578 ---> 074aa15fee4c Step 11/15 : COPY Pip* /app/ ---> 554f403d7b11 Step 12/15 : RUN pip install --upgrade pip && pip install pipenv && pipenv install --dev --system --deploy --ignore-pipfile ---> Running in 08faec42b7d5 Collecting pip Downloading https://files.pythonhosted.org/packages/d8/f3/413bab4ff08e1fc4828dfc59996d721917df8e8583ea85385d51125dceff/pip-19.0.3-py2.py3-none-any.whl (1.4MB) Installing collected packages: pip Found existing installation: pip 18.1 Uninstalling pip-18.1: Successfully uninstalled pip-18.1 Successfully installed pip-19.0.3 Collecting pipenv Downloading https://files.pythonhosted.org/packages/13/b4/3ffa55f77161cff9a5220f162670f7c5eb00df52e00939e203f601b0f579/pipenv-2018.11.26-py3-none-any.whl (5.2MB) Requirement already satisfied: pip>=9.0.1 in /usr/local/lib/python3.7/site-packages (from pipenv) (19.0.3) Collecting virtualenv-clone>=0.2.5 (from pipenv) Downloading https://files.pythonhosted.org/packages/e3/d9/d9c56deb483c4d3289a00b12046e41428be64e8236fa210111a1f57cc42d/virtualenv_clone-0.5.1-py2.py3-none-any.whl Collecting virtualenv (from pipenv) Downloading https://files.pythonhosted.org/packages/33/5d/314c760d4204f64e4a968275182b7751bd5c3249094757b39ba987dcfb5a/virtualenv-16.4.3-py2.py3-none-any.whl (2.0MB) Collecting certifi (from pipenv) Downloading https://files.pythonhosted.org/packages/9f/e0/accfc1b56b57e9750eba272e24c4dddeac86852c2bebd1236674d7887e8a/certifi-2018.11.29-py2.py3-none-any.whl (154kB) Requirement already satisfied: setuptools>=36.2.1 in /usr/local/lib/python3.7/site-packages (from pipenv) (40.6.2) Installing collected packages: virtualenv-clone, virtualenv, certifi, pipenv Successfully installed certifi-2018.11.29 pipenv-2018.11.26 virtualenv-16.4.3 virtualenv-clone-0.5.1 Installing dependencies from Pipfile.lock (9a5a3a)… Removing intermediate container 08faec42b7d5 ---> 740ed1329305 Step 13/15 : ADD . /app ---> 3551608282e2 Step 14/15 : EXPOSE 5000 ---> Running in 07b57fe6a5e7 Removing intermediate container 07b57fe6a5e7 ---> 2e07658bbae8 Step 15/15 : CMD flask run --host=0.0.0.0 ---> Running in bdf7404770f6 Removing intermediate container bdf7404770f6 ---> cf7d3ee68072 Successfully built cf7d3ee68072 Successfully tagged vm_docker_flask:latest
After that, we need to run our container. The follow Docker CLI command makes the magic:
docker run -d --name my_container_flask -p 5000:5000 vm_docker_flask
docker run runs our container, the flag
-d inform to Docker that is running in background, the flag
--name put the my_container_flask to our container, the flag
-p bind the port 5000 of container to port 5000 of host and, at the end, is the name of our image built in the
docker build command.
This command shows us this result in our terminal (or similar, because this command returns the container id):
Now we can do our first test, see if our container is running, for that we execute Docker CLI command like that:
pythonprojects/docker_flask_app [ docker container ps ] CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 880a5b59f2aa vm_docker_flask "/bin/sh -c 'flask r…" 15 minutes ago Up 15 minutes 0.0.0.0:5000->5000/tcp my_container_flask
This Docker CLI command shows us if our which containers are running and their info like Container ID, Image, Command and so on. In our case only my_container_flask how you can see above.
Another test that we can do is a simple HTTP request. I use httpie application in this test. See the terminal result:
pythonprojects/docker_flask_app [ http 0.0.0.0:5000 ] HTTP/1.0 200 OK Content-Length: 11 Content-Type: text/html; charset=utf-8 Date: Tue, 05 Mar 2019 18:17:24 GMT Server: Werkzeug/0.14.1 Python/3.7.1 Hello World
Now the most important test. We execute the pytest into our container to run tests against our Flask application. The test is just simple only assert if the status code returned by the Flask App is 200 Ok!
To do it we execute the follow Docker CLI command:
docker exec my_container_flask pytest
This docker CLI command runs pytest against my_container_flask and if all it is ok we have this terminal result:
=========================== test session starts ============================ platform linux -- Python 3.7.1, pytest-4.3.0, py-1.8.0, pluggy-0.9.0 rootdir: /app, inifile: pytest.ini plugins: sugar-0.9.2, flask-0.14.0 collected 1 item backend/blueprints/core/tests/test_bp_core.py . [100%] ========================= 1 passed in 0.06 seconds =========================
I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others. Thank for reading !
Are you looking for experienced, reliable, and qualified Python developers? If yes, you have reached the right place. At **[HourlyDeveloper.io](https://hourlydeveloper.io/ "HourlyDeveloper.io")**, our full-stack Python development services...
Looking to build robust, scalable, and dynamic responsive websites and applications in Python? At **[HourlyDeveloper.io](https://hourlydeveloper.io/ "HourlyDeveloper.io")**, we constantly endeavor to give you exactly what you need. If you need to...
In the programming world, Data types play an important role. Each Variable is stored in different data types and responsible for various functions. Python had two different objects, and They are mutable and immutable objects.
Looking for an attractive & user-friendly web developer? HourlyDeveloper.io, a leading web, and mobile app development company, offers web developers for hire through flexible engagement models. You can **[Hire Web...
After analyzing clients and market requirements, TopDevelopers has come up with the list of the best Python service providers. These top-rated Python developers are widely appreciated for their professionalism in handling diverse projects. When...