{"id":2920,"date":"2018-12-08T18:43:10","date_gmt":"2018-12-08T23:43:10","guid":{"rendered":"http:\/\/osric.com\/chris\/accidental-developer\/?p=2920"},"modified":"2018-12-08T19:55:51","modified_gmt":"2018-12-09T00:55:51","slug":"running-a-python-flask-application-in-a-docker-container","status":"publish","type":"post","link":"https:\/\/osric.com\/chris\/accidental-developer\/2018\/12\/running-a-python-flask-application-in-a-docker-container\/","title":{"rendered":"Running a Python Flask application in a Docker container"},"content":{"rendered":"<p>I&#8217;ve played with Docker containers but haven&#8217;t really done anything, useful or otherwise, with them. I decided to create a Docker image that includes a web-based chatbot. You can find the Git repository for this (including the finished <code>Dockerfile<\/code>) at <a href=\"https:\/\/github.com\/cherdt\/docker-nltk-chatbot\">https:\/\/github.com\/cherdt\/docker-nltk-chatbot<\/a><\/p>\n<p><!--more--><\/p>\n<p>I&#8217;ve worked with this particular chatbot before, which is based on the <a href=\"https:\/\/www.nltk.org\/api\/nltk.chat.html#module-nltk.chat.eliza\">nltk.chat.eliza<\/a> module. I turned it into a web application by wrapping it in a Flask app. And because Flask warns you not to run Flask directly in production, I call the Flask app via uWSGI.<\/p>\n<p>I started by creating a <code>Dockerfile<\/code>:<\/p>\n<pre><code># mkdir chatbot\r\n# cd chatbot\r\n# vi Dockerfile<\/code><\/pre>\n<p>I needed to start with a base image, so I picked CentOS 7:<\/p>\n<pre><code>FROM centos:centos7<\/code><\/pre>\n<p>I knew I would need several packages installed to satisfy the dependencies for Flask and uWSGI (although it took me a couple tries before I determined that I would need gcc and python-devel in order to install uWSGI):<\/p>\n<pre><code>RUN \/usr\/bin\/yum --assumeyes install epel-release gcc\r\nRUN \/usr\/bin\/yum --assumeyes install python python-devel python-pip<\/code><\/pre>\n<p>Then to install Flask and dependencies, uWSGI, and the Python NLTK (Natural Language ToolKit):<\/p>\n<pre><code>RUN \/usr\/bin\/pip install Flask flask-cors nltk requests uwsgi<\/code><\/pre>\n<p>The Python file for the Flask app and the HTML template file needed to be copied into the container:<\/p>\n<pre><code>COPY chatbot.py .\/\r\nCOPY chat.html .\/<\/code><\/pre>\n<p>Finally, the command that will run uWSGI and point it at the Flask app:<\/p>\n<pre><code>CMD [\"\/usr\/bin\/uwsgi\", \"--http\", \":9500\", \"--manage-script-name\", \"--mount\", \"\/=chatbot:app\"]<\/code><\/pre>\n<p>Note that CMD does not like spaces: each item needs to be a separate array element.<\/p>\n<p>Now to build the image:<\/p>\n<pre><code># docker build --tag chatbot .\r\n...[some other output]...\r\nSuccessfully built ab3a32938f0e\r\nSuccessfully tagged chatbot:latest\r\n\r\n# docker images\r\nREPOSITORY          TAG                 IMAGE ID            CREATED              SIZE\r\nchatbot             latest              ab3a32938f0e        5 seconds ago        486MB<\/code><\/pre>\n<p>To run a container based on this image, I wanted to do several things:<\/p>\n<ul>\n<li>Run the process in the background (detached mode), using <code>-d<\/code><\/li>\n<li>Restart the Docker container on errors, using <code>--restart on-failure<\/code><\/li>\n<li>Map port 80 on the host to 9500 (the listening port of uWSGI in the container) using <code>-p 80:9500<\/code><\/li>\n<\/ul>\n<pre><code># docker run -d --restart on-failure -p 80:9500 chatbot<\/code><\/pre>\n<p>Now, to test the application:<\/p>\n<pre><code># curl localhost\/chat-api?text=Does+this+work%3F\r\nPlease consider whether you can answer your own question.<\/code><\/pre>\n<p><em>Success!!!<\/em><\/p>\n<p><strong>Deploying to Production (or at least somewhere else)<\/strong><\/p>\n<p>Now that I had a working container, I wanted to deploy it somewhere other than my development environment. I created logged into the Docker Hub website and create a repository at <a href=\"https:\/\/hub.docker.com\/r\/cherdt\/nltk-chatbot\/\">cherdt\/nltk-chatbot<\/a>.<\/p>\n<p>To store the image, first I needed to log in:<\/p>\n<pre><code># docker login\r\nLogin 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.\r\nUsername: cherdt\r\nPassword:\r\nWARNING! Your password will be stored unencrypted in \/root\/.docker\/config.json.\r\nConfigure a credential helper to remove this warning. See\r\nhttps:\/\/docs.docker.com\/engine\/reference\/commandline\/login\/#credentials-store\r\n\r\nLogin Succeeded<\/code><\/pre>\n<p>Then I needed to tag my image with the repository name:<\/p>\n<pre><code># docker tag ab3a32938f0e cherdt\/nltk-chatbot<\/code><\/pre>\n<p>Then I was able to push the image to the repository:<\/p>\n<pre><code># docker push cherdt\/nltk-chatbot<\/code><\/pre>\n<p>I don&#8217;t have a production server where I want to run this, but as a proof-of-concept for myself I wanted to deploy it <em>somewhere<\/em>. I created another CentOS 7 virtual machine and installed Docker there (see <a href=\"https:\/\/docs.docker.com\/install\/linux\/docker-ce\/centos\/\">Get Docker CE for CentOS<\/a>).<\/p>\n<p>I pulled the image onto the new host. This did not require logging in, since it is a public repository:<\/p>\n<pre><code># docker pull cherdt\/nltk-chatbot<\/code><\/pre>\n<p>I ran a container the same way I had before, but updating the image name to match the repository:<\/p>\n<pre><code># docker run -d --restart on-failure -p 80:9500 cherdt\/nltk-chatbot<\/code><\/pre>\n<p>And the test?<\/p>\n<pre><code># curl localhost\/chat-api?text=Does+this+work%3F\r\nPlease consider whether you can answer your own question.<\/code><\/pre>\n<p><strong>Considerations<\/strong><\/p>\n<p>The deployment host needed to have Docker installed and the Docker daemon running, but none of the other dependencies were needed: gcc, python-devel, pip, Flask, uwsgi, etc. are all self-contained in the Docker image.<\/p>\n<p>On the other hand, the Docker image is just shy of 500 MB:<\/p>\n<pre><code># docker images\r\nREPOSITORY            TAG                 IMAGE ID            CREATED             SIZE\r\ncherdt\/nltk-chatbot   latest              ab3a32938f0e        28 minutes ago      486MB<\/code><\/pre>\n<p>That&#8217;s pretty heavy considering the chatbot.py file is 369 bytes! For a trivial proof-of-concept application that seems like a lot of overhead, but if this was a critical production application, or even if it&#8217;s something I planned to deploy several times, the amount of time saved in setting up and configuring new hosts would be worth it. It also means that the behavior of the application in my development environment should be the same as the behavior in the production environment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a demonstration of containerizing an application using Docker, I built a Docker image to run the classic ELIZA chatbot as a web application.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[451],"tags":[449,492,358,529],"class_list":["post-2920","post","type-post","status-publish","format-standard","hentry","category-docker","tag-docker","tag-flask","tag-python","tag-uwsgi"],"_links":{"self":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2920","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/comments?post=2920"}],"version-history":[{"count":6,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2920\/revisions"}],"predecessor-version":[{"id":2926,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2920\/revisions\/2926"}],"wp:attachment":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/media?parent=2920"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/categories?post=2920"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/tags?post=2920"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}