angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Python Flask app on Docker in ISPConfig3 with Nginx - Part 1: Minimal app

ISPConfig is a great hosting control panel but it does not support Python applications out of the box. This post shows how you can do it using Docker.

13 February 2019 Updated 31 August 2019
post main image

This is a post showing how to run a Flask app on ISPConfig3. Why? I have a VPS on the internet running Debian and ISPConfig3. It is running static sites and PHP sites. But now I also want to run my Flask python apps here. This way I can use the domain management I am used to and do not need an extra server for Python apps.

This solution uses Docker to run the Flask app, printing 'Hello world', and is a first proof of concept showing it is possible to deploy a Flasp app on ISPConfig3. 

My local machine:

  • Ubuntu desktop 18.04.1
  • Python  3.6.7
  • nginx 1.14.0
  • Docker 18.09.0
  • Docker-compose 1.23.2

My VPS:

  • Debian 9 (Stretch)
  • ISPConfig3 3.1.13
  • Nginx 1.10.3
  • docker 18.09.0
  • Docker-compose 1.23.2

Try to keep the Docker and Docker-compose versions on both machines identical. Docker developers sometimes fail to keep backward compatibility.

Contents:

  • Step 1: Create and run a Flask app helloworld on the local machine, using the development server
  • Step 2: Run helloworld on the local machine, using the production server Gunicorn
  • Step 3: Run helloworld on the local machine, using Docker
  • Step 4: On the local machine serve the dockarized helloworld with a reverse proxy nginx server
  • Step 5: On the ISPConfig3 machine, deploy the dockarized helloworld
  • Summary
  • Notes

Step 1: Create and run a Flask app helloworld on the local machine, using the development server

1.1. Create virtual environment and activate

See instructions on the internet. Do something like:

> mkdir flask_docker
> cd flask_docker
> mkdir python3
> cd python3
> mkdir venvs
> python3 -m venv venvs/helloworld
> source venvs/helloworld/bin/activate
> cd venvs/helloworld
> pwd
.../flask_docker/python3/venvs/helloworld

1.2. Check the directory helloworld

You should see something like:

.
|-- bin
|-- include
|-- lib
|-- lib64 -> lib
|-- __pycache__
|-- share
`-- pyvenv.cfg
All commands below are from this directory. 

1.3. Install Flask

Type:

> pip install flask

1.4. Create simple Flask application hello.py

With you editor create a Flask app helloworld.py:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return "<h1>Hello world!</h1>"
if __name__ == '__main__':
    app.run()

1.5. Run it using the development server

> python3 helloworld.py

In your browser type: localhost:5000

You should see: Hello World

Stop the development server using Ctrl-C.

Step 2: Run helloworld on the local machine, using the production server Gunicorn

2.1. Install production server gunicorn

pip install gunicorn

2.2. Create application object wsgi.py file for gunicorn:

Create the wsgi.py as follows:

from helloworld import app

if __name__ == "__main__":
    app.run()

We are simply importing the app from helloworld.py.

2.3. Run it using the gunicorn server

> gunicorn --bind 0.0.0.0:8000 wsgi:app

In your browser type: localhost:80000

You should see: Hello World

Stop the gunicorn server using Ctrl-C.

Step 3: Run helloworld on the local machine, using Docker

3.1. Install Docker and Docker-compose

See instructions on docker.com, digitalocean.com, etc.

If you cannot issue Docker commands without sudo, then add yourself to the Docker group:

Post-installation steps for Linux
https://docs.docker.com/install/linux/linux-postinstall/

3.2. Store the required software in requirements.txt

> pip freeze > requirements.txt

If there is a line 'pkg-resources==0.0.0' present in requirements.txt, remove it.
This is a bug on some systems.

My requirements.txt file look like this:

Click==7.0
Flask==1.0.2
gunicorn==19.9.0
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
Werkzeug==0.14.1   

3.3. Create a Dockerfile

The Dockerfile looks like this:

FROM python:3.6-alpine

RUN adduser -D helloworlduser

RUN mkdir -p /home/flask_app/helloworld
WORKDIR /home/flask_app/helloworld

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY helloworld.py wsgi.py boot.sh ./
RUN chmod +x boot.sh

ENV FLASK_APP helloworld.py

RUN chown -R helloworlduser:helloworlduser ./
USER helloworlduser

EXPOSE 5000
ENTRYPOINT ["./boot.sh"]

The start up script boot.sh:

#!/bin/sh
# called by Dockerfile

# go to directory where wsgi.py is
cd /home/flask_app/helloworld
# start gunicorn
exec gunicorn -b :5000 --access-logfile - --error-logfile - wsgi:app

The directory now looks like this:

.
|-- bin
|-- include
|-- lib
|-- lib64 -&gt; lib
|-- __pycache__
|-- share
|-- boot.sh
|-- docker-compose.yml
|-- Dockerfile
|-- Dockerfile_web
|-- helloworld.py
|-- image_helloworld.tar
|-- pyvenv.cfg
|-- requirements.txt
`-- wsgi.py

3.4. Build the container image

> docker build -t helloworld:latest .

The output ends with something like:

...
Successfully built d3e8bc220161
Successfully tagged helloworld:latest

You can check if the container has been created:

> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              d3e8bc220161        2 minutes ago       84.8MB
python              3.6-alpine          1837080c5e87        5 weeks ago         74.4MB

3.5. Run the container image

First run the container as a foreground application, i.e. without the -d option:

> docker run --name helloworld -p 8001:5000 --rm helloworld:latest

This will show debug information.  If something is wrong e.g. in boot.sh you can correct it and rebuild the container again:

> docker build -t helloworld:latest .

You can also enter the container to see what is happening, e.g. if the files are really there:

> docker run -it --entrypoint /bin/sh  --rm helloworld:latest

If everything is fine you can run the container in the backgrounf using the -d option:

> docker run --name helloworld -d -p 8001:5000 --rm helloworld:latest

In your browser type: localhost:80001

You should see: Hello World

Check that the container is running:

> docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
37f06d44cd30        helloworld:latest   "./boot.sh"         4 seconds ago       Up 4 seconds        0.0.0.0:8001->5000/tcp   helloworld

Check the logs:

> docker logs helloworld

[2019-02-02 14:52:57 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-02-02 14:52:57 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2019-02-02 14:52:57 +0000] [1] [INFO] Using worker: sync
[2019-02-02 14:52:57 +0000] [9] [INFO] Booting worker with pid: 9

To stop the container:

> docker kill helloworld

Check that the container stopped running:

> docker ps

Step 4: On the local machine serve the dockarized helloworld with a reverse proxy Nginx server

4.1. Install Nginx on the local machine

If it is not yet installed:

> sudo apt install nginx

4.2. Configure Nginx

We are going to serve the helloworld.net website at port 8080.  Configure Nginx as a reverse proxy, i.e. it directs client requests to our Gunicorn server running on Docker.

Create a file /etc/nginx/sites-available/helloworld.conf:

server {
  listen 8080;
  server_name helloworld.net;

  location / {
    proxy_pass http://127.0.0.1:8001;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 1M;  
  }
}

Create a symbolic link in /etc/nginx/sites-enabled:

> sudo ln -s /etc/nginx/sites-available/helloworld.conf /etc/nginx/sites-enabled/helloworld.conf

Restart Nginx:

> sudo systemctl restart nginx

If anything goes wrong, look at the status:

> sudo systemctl status nginx.service

4.3. Add helloworld.net to the hosts file

> sudo nano /etc/hosts

Add a line:

127.0.0.1    helloworld.net

Check it is working, it should respond 127.0.0.1 on ping:

> ping helloworld.net

PING helloworld.net (127.0.0.1) 56(84) bytes of data.
64 bytes from helloworld.net (127.0.0.1): icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from helloworld.net (127.0.0.1): icmp_seq=2 ttl=64 time=0.042 ms

In chromium (chrome) you can check and clear the dns by typing in the address bar:

chrome://net-internals/#dns

4.4. Start the container and access your website

In your browser type: helloworld.net:8080

If you did not start the helloworld docker container image yet, the browser will display:

502 Bad Gateway

Start your container:

> docker run --name helloworld -d -p 8001:5000 --rm helloworld:latest

In your browser you should see: Hello World

Step 5: On the ISPConfig3 VPS, deploy the dockarized helloworld

We now have a Docker container image that is working on our local machine. To run this on our ISPConfig3 machine we have several options. Here we use: copy the Docker image to another host.

5.1. Install Docker and Docker-compose on the VPS

Follow installation procedures.

5.2. Copy Dockerfile and docker image to ISPConfig3 machine

Locate our image:

> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              bee00f1c8607        21 hours ago        84.8MB

Save the docker image as a tar file:

> docker save -o ./image_helloworld.tar helloworld

Use a file transfer program to copy Dockerfile and the image to the ISPConfig3 machine.

On the ISPConfig3 machine go to the directory where the files are and add our image to Docker:

> docker load -i image_helloworld.tar

This may take some time. Check the image is available:

> docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              bee00f1c8607        21 hours ago        84.8MB

5.3. Configure your site in ISPConfig3

Assumption is you have a domain name already pointing to your ISPConfig3 machine, you added the domain, and added the site. In this case when typing in your browser:

<domain>

you shoud see something like:

Welcome to your website!

Login to ISPConfig3.

Go to: Sites->Your site

In the Domain tab:

- CGI uncheck

- PHP: disabled

Click Save.

In the Options tab

Add the following to nginx Directives:

location / {
    proxy_pass http://127.0.0.1:8001;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    client_max_body_size 1M;
}

Click Save.

If your site was working before it should now show:

ERROR 502 - Bad Gateway!

5.4. Start the container

> docker run --name helloworld -p 8001:5000 --rm helloworld:latest

Now after typing in your browser:

<domain>

you shoud see: Hello World

Great!

Stop the Docker Flask app:

> docker kill helloworld

Check it is gone:

> docker ps

Summary

We created a simple Flask app and deployed it on our ISPConfig3 machine.

In a next post we will use a more advanced example:

  • Serve multiple pages
  • Serve static files
  • Connect to the ISPConfig3 database

Notes

1. You may want to use Gunicorn always for development

Enabling the Flask Interactive Debugger in Development with Gunicorn
https://nickjanetakis.com/blog/enabling-the-flask-interactive-debugger-in-development-with-gunicorn


Links / credits

Deploy flask app with nginx using gunicorn and supervisor
https://medium.com/ymedialabs-innovation/deploy-flask-app-with-nginx-using-gunicorn-and-supervisor-d7a93aa07c18

Embedding Python In Apache2 With mod_python (Debian/Ubuntu, Fedora/CentOS, Mandriva, OpenSUSE)
https://www.howtoforge.com/embedding-python-in-apache2-with-mod_python-debian-ubuntu-fedora-centos-mandriva-opensuse

How to Configure NGINX for a Flask Web Application
https://www.patricksoftwareblog.com/how-to-configure-nginx-for-a-flask-web-application/" target="_blank">How to Configure NGINX for a Flask

How to copy Docker images from one host to another without using a repository
https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository

How to deploy a django application on a linux (Debian/Ubuntu) production server running ISPConfig 3
http://blog.yawd.eu/2011/how-to-deploy-django-app-linux-server-ispconfig/

How To Serve Flask Applications with Gunicorn and Nginx on Ubuntu 18.04
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-18-04

The Flask Mega-Tutorial Part XIX: Deployment on Docker Containers
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xix-deployment-on-docker-containers

Leave a comment

Comment anonymously or log in to comment.

Comments

Leave a reply

Reply anonymously or log in to reply.