{"id":572,"date":"2024-05-02T12:37:02","date_gmt":"2024-05-02T12:37:02","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=572"},"modified":"2024-05-02T12:37:02","modified_gmt":"2024-05-02T12:37:02","slug":"dockerize-python-flask-application","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=572","title":{"rendered":"Dockerize Flask Application: A Step-by-Step Guide"},"content":{"rendered":"<p>In this guide, we will look at a step-by-step guide to Dockerize Flask Application.<\/p>\n<p>I will cover everything from creating a basic Flask application to creating a production-grade Flask application Docker image that uses the <strong>Gunicorn<\/strong> server.<\/p>\n<p>By default, Flask uses the <strong>built-in development server<\/strong> provided by Werkzeug that can be used for development and testing purposes.<\/p>\n<p>So we will use <strong>Gunicorn<\/strong>, a production-grade server for Flask application that is capable of handling a higher volume of traffic, supporting multiple worker processes or threads, and provides <strong>better performance and scalability<\/strong>.<\/p>\n<p>Also, we will look at some of the best practices you can follow in the image-building process for your image-building CI pipeline.<\/p>\n<h2 id=\"prerequisites-repository\">Prerequisites &amp; Repository<\/h2>\n<p>Here are the prerequisites for Dockerizing the Python Flask application:<\/p>\n<ol>\n<li><a href=\"https:\/\/devopscube.com\/how-to-install-and-configure-docker\/\">Docker installed<\/a> in your system.<\/li>\n<li>A Python Flask application file that has to be Dockerized.<\/li>\n<\/ol>\n<p>All the code and configuration files used in this blog are hosted in the <a href=\"https:\/\/github.com\/techiescamp\/docker-image-examples\/tree\/main\/python-flask?ref=devopscube.com\">docker image examples repository.<\/a><\/p>\n<p>Clone\/Fork the repository to make use of it.<\/p>\n<pre><code>git clone https:\/\/github.com\/techiescamp\/docker-image-examples<\/code><\/pre>\n<h2 id=\"how-to-dockerize-python-flask-application\">How to Dockerize Python Flask Application?<\/h2>\n<p>Follow the steps given below to build a Docker image for a Flask application.<\/p>\n<h3 id=\"step-1-build-a-flask-application\">Step 1: Build a Flask Application<\/h3>\n<blockquote><p><strong>Note<\/strong>: Ignore this step if you have an existing Flask application.<\/p><\/blockquote>\n<p>For demonstration purposes, I am going to use a simple Hello World Flask application. The Python file is given below<\/p>\n<pre><code>from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route('\/')\ndef hello_world():\n    return render_template('index.html')\n\nif __name__ == '__main__':\n    app.run(host='0.0.0.0')<\/code><\/pre>\n<p>This file creates a Python Flask application that will display a custom message in the Homepage.<\/p>\n<p>Next, create a new HTML file named <code>index.html<\/code> in a directory named <code>templates<\/code> within your project directory. The <code>templates<\/code> directory is where Flask looks for HTML templates by default.<\/p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n    &lt;title&gt;Hello DevOps Engineer&lt;\/title&gt;\n    &lt;link rel=\"stylesheet\" href=\"https:\/\/maxcdn.bootstrapcdn.com\/bootstrap\/4.5.2\/css\/bootstrap.min.css\"&gt;\n    &lt;style&gt;\n        body {\n            background-color: #f8f9fa;\n        }\n        .header {\n            background-color: #343a40;\n            color: white;\n            padding: 20px;\n            text-align: center;\n            margin-bottom: 20px;\n        }\n        .message-box {\n            background-color: #fff;\n            border-radius: 5px;\n            padding: 20px;\n            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);\n        }\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div class=\"header\"&gt;\n        &lt;h1&gt;Welcome to My Flask App&lt;\/h1&gt;\n    &lt;\/div&gt;\n    &lt;div class=\"container\"&gt;\n        &lt;div class=\"row justify-content-center\"&gt;\n            &lt;div class=\"col-md-6\"&gt;\n                &lt;div class=\"message-box\"&gt;\n                    &lt;h2 class=\"text-center\"&gt;Hello DevOps Engineer&lt;\/h2&gt;\n                    &lt;p class=\"text-center\"&gt;This is a sample Flask application with Bootstrap styling.&lt;\/p&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p>Create a <code>requirements.txt<\/code> file in the project directory.<\/p>\n<pre><code>#requirements.txt\n\nflask\ngunicorn<\/code><\/pre>\n<h3 id=\"step-2-create-dockerfile\">Step 2: Create Dockerfile<\/h3>\n<p>Now, create a <code>Dockerfile<\/code> with the following content. You can also use the docker init command utility to generate a Dockerfile.<\/p>\n<pre><code>ARG PYTHON_VERSION=3.11.6\nFROM python:${PYTHON_VERSION}-slim as base\n\nENV PYTHONDONTWRITEBYTECODE=1\nENV PYTHONUNBUFFERED=1\n\nWORKDIR \/app\n\nARG UID=10001\nRUN adduser \\\n    --disabled-password \\\n    --gecos \"\" \\\n    --home \"\/nonexistent\" \\\n    --shell \"\/sbin\/nologin\" \\\n    --no-create-home \\\n    --uid \"${UID}\" \\\n    appuser\n\n# Install the dependencies\nRUN --mount=type=cache,target=\/root\/.cache\/pip \\\n    --mount=type=bind,source=requirements.txt,target=requirements.txt \\\n    python -m pip install -r requirements.txt\n\nUSER appuser\n\nCOPY . .\n\nEXPOSE 8000\n\n# Run the application using Gunicorn\nCMD [\"gunicorn\", \"--bind\", \"0.0.0.0:8000\", \"hello:app\"]<\/code><\/pre>\n<p>Here is the Dockerfile explanation.<\/p>\n<ol>\n<li>This Dockerfile builds a container image with <strong>Python Slim<\/strong> as its base image. Which is a minimal image.<\/li>\n<li>Then we set the<strong> \/app<\/strong> directory as the working directory.<\/li>\n<li>Next, we setup a non-root user named appuser that the app will run under (security Best practice)<\/li>\n<li><strong>&#8211;mount=type=cache,target=\/root\/.cache\/pip<\/strong>: This option creates a cache mount for the pip cache directory at <code>\/root\/.cache\/pip<\/code>. By using a cache mount, the pip cache is stored outside the container&#8217;s filesystem and can be reused across multiple builds.<\/li>\n<li><strong>&#8211;mount=type=bind,source=requirements.txt,target=requirements.txt<\/strong>: This option creates a bind mount that mounts the applications <code>requirements.txt<\/code> file from the host filesystem into the container at the same path. It allows the <code>pip install<\/code> command to access the <code>requirements.txt<\/code> file without copying it into the container&#8217;s filesystem. It ensures that the latest version of the <code>requirements.txt<\/code> file is always used during the build.<\/li>\n<li><strong>python -m pip install -r requirements.t<\/strong>xt installs the required python dependencies.<\/li>\n<li>Then we copy the <strong>hello.py<\/strong> (application code) to the working directory.<\/li>\n<li>Since we are using Gunicorn, we <strong>will expose port 8000.<\/strong> The default Flask app port is 5000.<\/li>\n<li>Finally using the CMD instruction, we run the gunicorn executable binding to <strong>0.0.0.0<\/strong>.<\/li>\n<\/ol>\n<h3 id=\"step-3-build-docker-image\">Step 3: Build Docker Image<\/h3>\n<p>To <a href=\"https:\/\/devopscube.com\/build-docker-image\/\">create the Docker image<\/a>, run the following command in the same directory where the Dockerfile is<\/p>\n<pre><code>docker build -t flask-application:1.0.0 .<\/code><\/pre>\n<p>To view the docker image, use the <strong>docker images<\/strong> command as shown below<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-261.png\" class=\"kg-image\" alt=\"docker images command \" loading=\"lazy\" width=\"2000\" height=\"878\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-261.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-261.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-261.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-261.png 2150w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h3 id=\"step-4-run-the-docker-image\">Step 4: Run the Docker Image<\/h3>\n<p>Once the Docker image build has been finished, run the Docker image using the command below.<\/p>\n<pre><code>docker run -d -p 8000:8000 flask-application:1.0.0<\/code><\/pre>\n<p>Once you run the docker image, use the <strong>docker ps<\/strong> command to verify if the container is running properly.<\/p>\n<p>Now, check your application on the browser using <strong>localhost:8000<\/strong>. If you expose your application on a different port make sure to change the port number you exposed the application to.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-2-27.png\" class=\"kg-image\" alt=\"Validating flask docker image through running application.\" loading=\"lazy\" width=\"620\" height=\"419\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-2-27.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-2-27.png 620w\"><\/figure>\n<p>You can also build a Docker image of any Python Flask application using these steps.<\/p>\n<h2 id=\"best-practices-for-optimizing-docker-images\">Best Practices for Optimizing Docker Images<\/h2>\n<p>When working with Docker images for production workloads, ensure you follow the Docker image best practices given below.<\/p>\n<p>These are particularly required in the CI pipeline where you build the flask image for production use cases.<\/p>\n<h3 id=\"1-lint-dockerfile\">1. Lint Dockerfile<\/h3>\n<p>Use <a href=\"https:\/\/devopscube.com\/lint-dockerfiles-using-hadolint\/\">Hadolint<\/a> to check your Dockerfile for possible errors, security vulnerabilities, and performance problems. Install Hadolint and use the below command to lint your Dockerfile<\/p>\n<pre><code>hadolint Dockerfile<\/code><\/pre>\n<p>Make sure to run this command in the same directory where your Dockerfile is and you will get the output as shown below to improve your Dockerfile<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-14-30.png\" class=\"kg-image\" alt=\"linting dockerfile\" loading=\"lazy\" width=\"1772\" height=\"758\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-14-30.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-14-30.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-14-30.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-14-30.png 1772w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h3 id=\"2-scan-for-vulnerabilities\">2. Scan for Vulnerabilities<\/h3>\n<p>Use tools like <a href=\"https:\/\/devopscube.com\/scan-docker-images-using-trivy\/\">Trivy<\/a> or Docker Scout to scan the Docker images for vulnerabilities. Install trivy in your system and use the below command to scan your Docker image<\/p>\n<pre><code>trivy image flask-application:1.0.0<\/code><\/pre>\n<p>Trivy will scan your Docker image and show the vulnerability in your Docker image as shown below<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-15-20.png\" class=\"kg-image\" alt=\"scan for docker image vulnerability using trivy\" loading=\"lazy\" width=\"667\" height=\"335\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-15-20.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-15-20.png 667w\"><\/figure>\n<h3 id=\"3-optimize-docker-image-size\">3. Optimize Docker Image Size<\/h3>\n<p><a href=\"https:\/\/devopscube.com\/reduce-docker-image-size\/\">Optimizing Docker images<\/a> is very much important in project environments, you can also use <a href=\"https:\/\/github.com\/slimtoolkit\/slim?ref=devopscube.com\" rel=\"noreferrer noopener\">SlimToolkit<\/a> to reduce the size of your Docker image.<\/p>\n<p>First, create a<strong><code> <\/code>preserved-paths.txt<\/strong> file with the file name or file path you don\u2019t want to get removed during the slim process.<\/p>\n<pre><code>\/app\n\/usr\/bin\/python3\n\/usr\/local\/bin\/flask<\/code><\/pre>\n<p>In my <strong>Txt<\/strong> file, I have specified the path where my application and files needed to run the Flask application are present. Now, run the below command to slim the Docker image with the Txt file.<\/p>\n<pre><code>slim build --http-probe=false --preserve-path-file preserved-paths.txt --target flask-application:1.0.0 --tag slimmed-flask-application:1.0.0<\/code><\/pre>\n<p>This command will slim the Docker images without affecting the file or file path mentioned in the <strong>txt<\/strong> file with a new name you specify in the <strong>&#8211;tag<\/strong> flag.<\/p>\n<p>Now if you you check the docker image size, it will be considerably reduced as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-16-26.png\" class=\"kg-image\" alt=\"size comparison of docker image before and after slimming the image\" loading=\"lazy\" width=\"1390\" height=\"502\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-16-26.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-16-26.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-16-26.png 1390w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>You can see the size of the Docker image has been reduced from <strong>480MB to 21MB<\/strong> and it works properly without any issue.<\/p>\n<h3 id=\"4-versioning-docker-image\">4. Versioning Docker Image<\/h3>\n<p>Use <a href=\"https:\/\/semver.org\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Semantic Versioning<\/a> when tagging Docker images to ensure clarity and consistency in versioning practices. An example is given below,<\/p>\n<pre><code>flask-application:1.0.0<\/code><\/pre>\n<p>In the above example, <strong>1.0.0<\/strong> is the semantic version.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>In this blog, we can see about the Dockerize Python Flask application and best practices for building secure Docker images.<\/p>\n<p>Make sure to follow the best practices whenever you build Docker images.<\/p>\n<p>If you need a tutorial blog to Dockerize Java Applications, refer <a href=\"https:\/\/devopscube.com\/dockerize-java-application\/\">this<\/a> blog.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/dockerize-python-flask-application\/\" target=\"_blank\" rel=\"noopener noreferrer\">Dockerize Flask Application: A Step-by-Step Guide \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/dockerize-python-flask-application\/<\/p>\n","protected":false},"author":1,"featured_media":573,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-572","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/572","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=572"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/572\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/573"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=572"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=572"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=572"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}