A Docker image contains the code used to create a Docker container, such as creating a Nginx web server, or a mySQL server, or a home grown app, and the list goes on. In this way, an image is like a template used to create a container. An image is kind of like a virtual machine, but much more light weight, using significantly less storage a memory (containers are usually megabytes in size).

There are two Docker repo's that are commonly used for Flask:
- tiangolo/uwsgi-nginx-flask (this article)
- tiangolo/meinheld-gunicorn-flask
According to https://hub.docker.com/r/tiangolo/uwsgi-nginx-flask:
"tiangolo/meinheld-gunicorn-flask will give you about 400% (4x) the performance of this tiangolo/uwsgi-nginx-flask"
So unless there is some reason you must use tiangolo/uwsgi-nginx-flask, I would go with tiangolo/meinheld-gunicorn-flask.
The docker pull command can be used to pull down the latest uwsgi-nginx-flask image.
sudo docker pull tiangolo/uwsgi-nginx-flask:latest
Or, create a file named Dockerfile so that the Dockerfile contains the following.
FROM tiangolo/uwsgi-nginx-flask:latest
Then use the docker build command to create the image, running this command in the same directory as the Dockerfile.
docker build --tag uwsgi-nginx-flask-python3.11 .
The docker images command can be used to display the image.
~]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tiangolo/uwsgi-nginx-flask latest 60b476e79885 5 days ago 968MB
Create a file somewhere on your Docker system such as /usr/local/docker/flask/main.py that contains the following.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World"
if __name__ == "__main__":
app.run(host='10.11.12.13', debug=True, port=80)The following command can then be used to create and start the container. Let's break down this command.
- The docker run command is used to create and start the container.
- The --detach flag is used to run the container in the background.
- The --publish option is used both the Docker server and container to listen on HTTP port 80, which adds a rule to iptables to allow connections between the Docker system and container on port 80.
- The --volume option is used to mount the main.py file on your Docker system to /app/main.py in the container.
- The --name option is used to give the container a specific name.
- The --restart unless-stopped option is used so that the container is started if the Docker server is restarted
- The uWSGI Nginx Flask image is used.
sudo docker run \
--detach \
--restart unless-stopped \
--publish 10.11.12.13:80:80 \
--volume /usr/local/docker/main.py:/app/main.py \
--name flask \
tiangolo/uwsgi-nginx-flask:latest
The docker container ls command can be used to ensure the container is running.
~]$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be0cc8e9aa4d uwsgi-nginx-flask "/entrypoint.sh /sta…" About a minute ago Up About a minute 443/tcp, 0.0.0.0:80->80/tcp flask
The docker logs command should return something like this.
~]# sudo docker logs uwsgi-nginx-flask
2022-12-17 03:47:14,238 INFO success: quit_on_failure entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
If running Docker on a Linux system, ensure connections are allowed in firewalld or iptables on ports 80 and 443.
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
If running Docker on an Amazon Web Services (AWS) EC2 Instance, ensure the Security Group Inbound Rules allow connections on ports 80 and 443.

You should then be able to access main.py should be returned to return Hello World at http://<hostname or IP address of your Docker system>.

And now the docker logs command should return something like this, the nginx log showing the GET request to the / endpoint.
~]# sudo docker logs uwsgi-nginx-flask
192.168.0.193 - - [23/Apr/2024:00:18:37 +0000] "GET / HTTP/1.1" 200 164 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0" "-"
[pid: 13|app: 0|req: 3/5] 192.168.0.193 () {42 vars in 828 bytes} [Tue Apr 23 00:18:36 2024] GET / => generated 164 bytes in 254 msecs (HTTP/1.1 200) 2 headers in 80 bytes (1 switches on core 0)
mounted files ownership
It is noteworthy that with the Docker --volume and --mount options, if the file on the Docker system is not owned by the user that owns the /app directory in the container, the files will default to be owned by the non-existent 1000 user and group in the container.
~]$ sudo docker exec my-container ls -l /app
-rw-r--r--. 1 1000 1000 103 Nov 30 10:58 main.py
-rw-r--r--. 1 www-data www-data 202 Nov 25 16:17 prestart.sh
-rw-r--r--. 1 www-data www-data 37 Nov 25 16:17 uwsgi.ini
In this example, the /app directory in the container is owned by user www-data and group www-data.
~]$ sudo docker exec my-container ls -ld /app
drwxr-xr-x. 1 www-data www-data 94 Nov 30 11:18 /app
Because the /etc/uwsgi/uwsgi.ini file in the container has uid = www-data and gid = www-data so that uwsgi in the container is run by use www-data instead of root.
~]$ cat /usr/local/docker/flask/build/uwsgi.ini
[uwsgi]
socket = /tmp/uwsgi.sock
chown-socket = nginx:nginx
chmod-socket = 664
# Graceful shutdown on SIGTERM, see https://github.com/unbit/uwsgi/issues/849#issuecomment-118869386
hook-master-start = unix_signal:15 gracefully_kill_them_all
need-app = true
die-on-term = true
# For debugging and testing
show-config = true
# by default, uwsgi in the container runs as root, this instead runs uwsgi in the container as www-data
uid = www-data
gid = www-data
Which was accomplished by having the following in the Docker file that was used to build the uwsgi nginx flask image.
COPY uwsgi.ini /etc/uwsgi/uwsgi.ini
This github issue has a debate on running uwsgi as root.
https://github.com/tiangolo/uwsgi-nginx-flask-docker/issues/66
The www-data user account UID is 33 and the www-data group GID is 33.
~]$ sudo docker exec my-container cat /etc/passwd | grep www-data
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
~]$ sudo docker exec my-container cat /etc/group | grep www-data
www-data:x:33:
Thus it may make sense to create www-data user and group on your Docker system.
sudo groupadd --system --gid 33 www-data
sudo useradd --home-dir /home/www-data --shell /bin/bash --gid www-data -c "files in uwsgi flask containers are owned by www-data" --system --uid 33 www-data
And then update the files on the Docker host system that are going to be mounted in the container to be owned by www-data.
sudo chown www-data /usr/local/docker/main.py
sudo chgrp www-data /usr/local/docker/main.py
Which should result in the file in the container being owned by www-data user and group.
~]$ sudo docker exec my-container ls -l /app
-rw-r--r--. 1 www-data www=data 103 Nov 30 10:58 main.py
-rw-r--r--. 1 www-data www-data 202 Nov 25 16:17 prestart.sh
-rw-r--r--. 1 www-data www-data 37 Nov 25 16:17 uwsgi.ini
Did you find this article helpful?
If so, consider buying me a coffee over at 