The author selected the Internet Archive to receive a donation as part of the Write for DOnations program.
作者选择了Internet存档作为“ Write for DOnations”计划的一部分来接受捐赠。
Developing web applications can become complex and time consuming when building and maintaining a number of different technologies. Considering lighter weight options designed to reduce complexity and time-to-production for your application can result in a more flexible and scalable solution. As a micro web framework built on Python, Flask provides an extensible way for developers to grow their applications through extensions that can be integrated into projects. To continue the scalability of a developer’s tech stack, MongoDB is a NoSQL database is designed to scale and work with frequent changes. Developers can use Docker to simplify the process of packaging and deploying their applications.
在构建和维护许多不同的技术时,开发Web应用程序会变得复杂且耗时。 考虑为减轻应用程序的复杂性和缩短生产时间而设计的重量更轻的选件,可以使解决方案更加灵活和可扩展。 作为基于Python构建的微型Web框架, Flask为开发人员提供了可扩展的方式,可通过可集成到项目中的扩展来扩展其应用程序。 为了继续提供开发人员技术堆栈的可伸缩性, MongoDB是一个NoSQL数据库,旨在进行扩展并应对频繁的更改。 开发人员可以使用Docker简化打包和部署其应用程序的过程。
Docker Compose has further simplified the development environment by allowing you to define your infrastructure, including your application services, network volumes, and bind mounts, in a single file. Using Docker Compose provides ease of use over running multiple docker container run
commands. It allows you to define all your services in a single Compose file, and with a single command you create and start all the services from your configuration. This ensures that there is version control throughout your container infrastructure. Docker Compose uses a project name to isolate environments from each other, this allows you to run multiple environments on a single host.
Docker Compose通过允许您在单个文件中定义基础架构(包括应用程序服务,网络卷和绑定挂载),进一步简化了开发环境。 通过使用Docker Compose,可以轻松运行多个Docker docker container run
命令。 它允许您在单个Compose文件中定义所有服务,并使用单个命令创建并从配置中启动所有服务。 这样可以确保在整个容器基础结构中进行版本控制。 Docker Compose使用项目名称将环境彼此隔离,这使您可以在单个主机上运行多个环境。
In this tutorial you will build, package, and run your to-do web application with Flask, Nginx, and MongoDB inside of Docker containers. You will define the entire stack configuration in a docker-compose.yml
file, along with configuration files for Python, MongoDB, and Nginx. Flask requires a web server to serve HTTP requests, so you will also use Gunicorn, which is a Python WSGI HTTP Server, to serve the application. Nginx acts as a reverse proxy server that forwards requests to Gunicorn for processing.
在本教程中,您将使用Docker 容器中的 Flask,Nginx和MongoDB构建,打包和运行待办Web应用程序。 您将在docker-compose.yml
文件中定义整个堆栈配置,以及Python,MongoDB和Nginx的配置文件。 Flask需要一个Web服务器来处理HTTP请求,因此您还将使用Gunicorn (它是Python WSGI HTTP Server)来为该应用程序提供服务。 Nginx充当反向代理服务器,将请求转发到Gunicorn进行处理。
To follow this tutorial, you will need the following:
要遵循本教程,您将需要以下内容:
A non-root user with sudo
privileges configured by following the steps in the Initial Server Setup tutorial.
通过执行“ 初始服务器设置”教程中的步骤配置的具有sudo
特权的非root用户。
Docker installed with the instructions from Step 1 and Step 2 of How To Install and Use Docker.
Docker安装了如何安装和使用Docker的步骤1和步骤2中的说明。
Docker Compose installed with the instructions from Step 1 of How To Install Docker Compose.
按照如何安装Docker Compose的步骤1中的说明安装了Docker Compose 。
Building your applications on Docker allows you to version infrastructure easily depending on configuration changes you make in Docker Compose. The infrastructure can be defined in a single file and built with a single command. In this step, you will set up the docker-compose.yml
file to run your Flask application.
在Docker上构建应用程序使您可以根据在Docker Compose中进行的配置更改轻松地对基础结构进行版本控制。 可以在单个文件中定义基础结构,并使用单个命令构建基础结构。 在此步骤中,您将设置docker-compose.yml
文件以运行Flask应用程序。
The docker-compose.yml
file lets you define your application infrastructure as individual services. The services can be connected to each other and each can have a volume attached to it for persistent storage. Volumes are stored in a part of the host filesystem managed by Docker (/var/lib/docker/volumes/
on Linux).
docker-compose.yml
文件使您可以将应用程序基础结构定义为单独的服务。 这些服务可以彼此连接,并且每个服务都可以连接一个卷以进行持久存储。 卷存储在由Docker管理的主机文件系统的一部分中(在Linux上为/var/lib/docker/volumes/
)。
Volumes are the best way to persist data in Docker, as the data in the volumes can be exported or shared with other applications. For additional information about sharing data in Docker, you can refer to How To Share Data Between the Docker Container and the Host.
卷是在Docker中持久保存数据的最佳方法,因为卷中的数据可以导出或与其他应用程序共享。 有关在Docker中共享数据的更多信息,您可以参考如何在Docker容器和主机之间共享数据 。
To get started, create a directory for the application in the home directory on your server:
首先,在服务器的主目录中为应用程序创建目录:
Move into the newly created directory:
移至新创建的目录:
Next, create the docker-compose.yml
file:
接下来,创建docker-compose.yml
文件:
The docker-compose.yml
file starts with a version number that identifies the Docker Compose file version. Docker Compose file version 3
targets Docker Engine version 1.13.0+
, which is a prerequisite for this setup. You will also add the services
tag that you will define in the next step:
docker-compose.yml
文件以标识Docker Compose文件版本的版本号开头。 Docker Compose文件版本3
针对Docker Engine版本1.13.0+
,这是此设置的先决条件。 您还将添加将在下一步中定义的services
标签:
version: '3'
services:
You will now define flask
as the first service in your docker-compose.yml
file. Add the following code to define the Flask service:
现在,您将在自己docker-compose.yml
文件中将flask
定义为第一个服务。 添加以下代码以定义Flask服务:
. . .
flask:
build:
context: app
dockerfile: Dockerfile
container_name: flask
image: digitalocean.com/flask-python:3.6
restart: unless-stopped
environment:
APP_ENV: "prod"
APP_DEBUG: "False"
APP_PORT: 5000
MONGODB_DATABASE: flaskdb
MONGODB_USERNAME: flaskuser
MONGODB_PASSWORD: your_mongodb_password
MONGODB_HOSTNAME: mongodb
volumes:
- appdata:/var/www
depends_on:
- mongodb
networks:
- frontend
- backend
The build
property defines the context
of the build. In this case, the app
folder that will contain the Dockerfile
.
build
属性定义了build
的context
。 在这种情况下,将包含Dockerfile
的app
文件夹。
You use the container_name
property to define a name for each container. The image
property specifies the image name and what the Docker image will be tagged as. The restart
property defines how the container should be restarted—in your case it is unless-stopped
. This means your containers will only be stopped when the Docker Engine is stopped/restarted or when you explicitly stop the containers. The benefit of using the unless-stopped
property is that the containers will start automatically once the Docker Engine is restarted or any error occurs.
您可以使用container_name
属性为每个容器定义一个名称。 image
属性指定映像名称以及将Docker映像标记为的名称。 restart
属性定义了容器的重新启动方式,在这种情况下, unless-stopped
应重新启动。 这意味着只有在停止/重新启动Docker Engine或显式停止容器时,才会停止容器。 使用unless-stopped
属性的好处是,一旦Docker Engine重新启动或发生任何错误,容器将自动启动。
The environment
property contains the environment variables that are passed to the container. You need to provide a secure password for the environment variable MONGODB_PASSWORD
. The volumes
property defines the volumes the service is using. In your case the volume appdata
is mounted inside the container at the /var/www
directory. The depends_on
property defines a service that Flask depends on to function properly. In this case, the flask
service will depend on mongodb
since the mongodb
service acts as the database for your application. depends_on
ensures that the flask
service only runs if the mongodb
service is running.
environment
属性包含传递到容器的环境变量。 您需要为环境变量MONGODB_PASSWORD
提供安全密码。 volumes
属性定义服务正在使用的卷。 在您的情况下,卷appdata
安装在/var/www
目录的容器内。 depends_on
属性定义Flask正常运行所依赖的服务。 在这种情况下, flask
服务将依赖于mongodb
因为mongodb
服务充当您应用程序的数据库。 depends_on
确保flask
服务仅在mongodb
服务正在运行时运行。
The networks
property specifies frontend
and backend
as the networks the flask
service will have access to.
networks
属性将frontend
和backend
指定为flask
服务将访问的网络。
With the flask
service defined, you’re ready to add the MongoDB configuration to the file. In this example, you will use the official 4.0.8
version mongo
image. Add the following code to your docker-compose.yml
file following the flask service
:
定义了flask
服务之后,就可以将MongoDB配置添加到文件中了。 在此示例中,您将使用官方4.0.8
版本的mongo
映像。 在flask service
之后,将以下代码添加到您docker-compose.yml
文件flask service
:
. . .
mongodb:
image: mongo:4.0.8
container_name: mongodb
restart: unless-stopped
command: mongod --auth
environment:
MONGO_INITDB_ROOT_USERNAME: mongodbuser
MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
MONGO_INITDB_DATABASE: flaskdb
MONGODB_DATA_DIR: /data/db
MONDODB_LOG_DIR: /dev/null
volumes:
- mongodbdata:/data/db
networks:
- backend
The container_name
for this service is mongodb
with a restart policy of unless-stopped
. You use the command
property to define the command that will be executed when the container is started. The command mongod --auth
will disable logging into the MongoDB shell without credentials, which will secure MongoDB by requiring authentication.
该服务的container_name
是mongodb
,其重启策略为unless-stopped
。 您可以使用command
属性定义启动容器时将要执行的命令。 命令mongod --auth
将禁用没有凭据的登录到MongoDB shell,这将通过要求身份验证来保护MongoDB。
The environment variables MONGO_INITDB_ROOT_USERNAME
and MONGO_INITDB_ROOT_PASSWORD
create a root user with the given credentials, so be sure to replace the placeholder with a strong password.
环境变量MONGO_INITDB_ROOT_USERNAME
和MONGO_INITDB_ROOT_PASSWORD
用给定的凭据创建一个根用户,因此请确保使用强密码替换占位符。
MongoDB stores its data in /data/db
by default, therefore the data in the /data/db
folder will be written to the named volume mongodbdata
for persistence. As a result you won’t lose your databases in the event of a restart. The mongoDB
service does not expose any ports, so the service will only be accessible through the backend
network.
MongoDB默认情况下将其数据存储在/data/db
,因此/data/db
文件夹中的/data/db
将被写入命名卷mongodbdata
以实现持久性。 因此,重新启动时不会丢失数据库。 mongoDB
服务不会公开任何端口,因此只能通过backend
网络访问该服务。
Next, you will define the web server for your application. Add the following code to your docker-compose.yml
file to configure Nginx:
接下来,您将为您的应用程序定义Web服务器。 将以下代码添加到您docker-compose.yml
文件中以配置Nginx:
. . .
webserver:
build:
context: nginx
dockerfile: Dockerfile
image: digitalocean.com/webserver:latest
container_name: webserver
restart: unless-stopped
environment:
APP_ENV: "prod"
APP_NAME: "webserver"
APP_DEBUG: "false"
SERVICE_NAME: "webserver"
ports:
- "80:80"
- "443:443"
volumes:
- nginxdata:/var/log/nginx
depends_on:
- flask
networks:
- frontend
Here you have defined the context
of the build
, which is the nginx
folder containing the Dockerfile
. With the image
property, you specify the image used to tag and run the container. The ports
property will configure the Nginx service to be publicly accessible through :80
and :443
and volumes
mounts the nginxdata
volume inside the container at /var/log/nginx
directory.
在这里,您定义了build
的context
,该context
是包含Dockerfile
的nginx
文件夹。 使用image
属性,可以指定用于标记和运行容器的图像。 ports
属性将Nginx服务配置为可通过:80
和:443
公开访问,并且volumes
将nginxdata
volumes
挂载在/var/log/nginx
目录中的容器内。
You’ve defined the service on which the web server service depends_on
as flask
. Finally the networks
property defines the networks web server service will have access to the frontend
.
您已经将Web服务器服务所depends_on
服务depends_on
为flask
。 最后, networks
属性定义了网络Web服务器服务将有权访问frontend
。
Next, you will create bridge networks to allow the containers to communicate with each other. Append the following lines to the end of your file:
接下来,您将创建网桥网络,以允许容器彼此通信。 将以下行添加到文件末尾:
. . .
networks:
frontend:
driver: bridge
backend:
driver: bridge
You defined two networks—frontend
and backend
—for the services to connect to. The front-end services, such as Nginx, will connect to the frontend
network since it needs to be publicly accessible. Back-end services, such as MongoDB, will connect to the backend
network to prevent unauthorized access to the service.
您为要连接的服务定义了两个网络( frontend
和backend
。 前端服务(例如Nginx)将连接到frontend
网络,因为它需要公开访问。 后端服务(例如MongoDB)将连接到backend
网络,以防止对服务的未经授权的访问。
Next, you will use volumes to persist the database, application, and configuration files. Since your application will use the databases and files, it is imperative to persist the changes made to them. The volumes are managed by Docker and stored on the filesystem. Add this code to the docker-compose.yml
file to configure the volumes:
接下来,您将使用卷来持久化数据库,应用程序和配置文件。 由于您的应用程序将使用数据库和文件,因此必须保留对其所做的更改。 卷由Docker管理并存储在文件系统中。 将此代码添加到docker-compose.yml
文件以配置卷:
. . .
volumes:
mongodbdata:
driver: local
appdata:
driver: local
nginxdata:
driver: local
The volumes
section declares the volumes that the application will use to persist data. Here you have defined the volumes mongodbdata
, appdata
, and nginxdata
for persisting your MongoDB databases, Flask application data, and the Nginx web server logs, respectively. All of these volumes use a local
driver to store the data locally. The volumes are used to persist this data so that data like your MongoDB databases and Nginx webserver logs could be lost once you restart the containers.
volumes
部分声明应用程序将用于保留数据的卷。 在这里,您已经定义了卷mongodbdata
, appdata
和nginxdata
用于分别保留MongoDB数据库,Flask应用程序数据和Nginx Web服务器日志。 所有这些卷都使用local
驱动程序在本地存储数据。 这些卷用于保留这些数据,以便在重启容器后可能会丢失诸如MongoDB数据库和Nginx Web服务器日志之类的数据。
Your complete docker-compose.yml
file will look like this:
您完整docker-compose.yml
文件将如下所示:
version: '3'
services:
flask:
build:
context: app
dockerfile: Dockerfile
container_name: flask
image: digitalocean.com/flask-python:3.6
restart: unless-stopped
environment:
APP_ENV: "prod"
APP_DEBUG: "False"
APP_PORT: 5000
MONGODB_DATABASE: flaskdb
MONGODB_USERNAME: flaskuser
MONGODB_PASSWORD: your_mongodb_password
MONGODB_HOSTNAME: mongodb
volumes:
- appdata:/var/www
depends_on:
- mongodb
networks:
- frontend
- backend
mongodb:
image: mongo:4.0.8
container_name: mongodb
restart: unless-stopped
command: mongod --auth
environment:
MONGO_INITDB_ROOT_USERNAME: mongodbuser
MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
MONGO_INITDB_DATABASE: flaskdb
MONGODB_DATA_DIR: /data/db
MONDODB_LOG_DIR: /dev/null
volumes:
- mongodbdata:/data/db
networks:
- backend
webserver:
build:
context: nginx
dockerfile: Dockerfile
image: digitalocean.com/webserver:latest
container_name: webserver
restart: unless-stopped
environment:
APP_ENV: "prod"
APP_NAME: "webserver"
APP_DEBUG: "true"
SERVICE_NAME: "webserver"
ports:
- "80:80"
- "443:443"
volumes:
- nginxdata:/var/log/nginx
depends_on:
- flask
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
volumes:
mongodbdata:
driver: local
appdata:
driver: local
nginxdata:
driver: local
Save the file and exit the editor after verifying your configuration.
验证配置后,保存文件并退出编辑器。
You’ve defined the Docker configuration for your entire application stack in the docker-compose.yml
file. You will now move on to writing the Dockerfiles for Flask and the web server.
您已经在docker-compose.yml
文件中为整个应用程序堆栈定义了Docker配置。 现在,您将继续为Flask和Web服务器编写Dockerfile。
With Docker, you can build containers to run your applications from a file called Dockerfile. The Dockerfile is a tool that enables you to create custom images that you can use to install the software required by your application and configure your containers based on your requirements. You can push the custom images you create to Docker Hub or any private registry.
使用Docker,您可以构建容器来从名为Dockerfile的文件运行应用程序。 Dockerfile是使您能够创建自定义映像的工具,可用于安装应用程序所需的软件并根据需要配置容器。 您可以将创建的自定义映像推送到Docker Hub或任何私有注册表。
In this step, you’ll write the Dockerfiles for the Flask and web server services. To get started, create the app
directory for your Flask application:
在此步骤中,您将为Flask和Web服务器服务编写Dockerfile。 首先,为Flask应用程序创建app
目录:
Next, create the Dockerfile
for your Flask app in the app
directory:
接下来,在app
目录中为Flask应用创建Dockerfile
:
Add the following code to the file to customize your Flask container:
将以下代码添加到文件中以自定义Flask容器:
FROM python:3.6.8-alpine3.9
LABEL MAINTAINER="FirstName LastName "
ENV GROUP_ID=1000 \
USER_ID=1000
WORKDIR /var/www/
In this Dockerfile
, you are creating an image on top of the 3.6.8-alpine3.9
image that is based on Alpine 3.9 with Python 3.6.8 pre-installed.
在此Dockerfile
,您将在3.6.8-alpine3.9
映像之上创建映像,该映像基于预装有Python 3.6.8的Alpine 3.9。
The ENV
directive is used to define the environment variables for our group and user ID. Linux Standard Base (LSB) specifies that UIDs and GIDs 0-99 are statically allocated by the system. UIDs 100-999 are supposed to be allocated dynamically for system users and groups. UIDs 1000-59999 are supposed to be dynamically allocated for user accounts. Keeping this in mind you can safely assign a UID and GID of 1000
, furthermore you can change the UID/GID by updating the GROUP_ID
and USER_ID
to match your requirements.
ENV
指令用于为我们的组和用户ID定义环境变量。 Linux Standard Base(LSB)指定UID和GID 0-99由系统静态分配。 应该为系统用户和组动态分配UID 100-999 。 应该为用户帐户动态分配UID 1000-59999 。 请记住,您可以安全地将UID和GID分配为1000
,此外,可以通过更新GROUP_ID
和USER_ID
来更改UID / GID以符合您的要求。
The WORKDIR
directive defines the working directory for the container. Be sure to replace the LABEL MAINTAINER
field with your name and email address.
WORKDIR
指令定义了容器的工作目录。 确保将LABEL MAINTAINER
字段替换为您的姓名和电子邮件地址。
Add the following code block to copy the Flask application into the container and install the necessary dependencies:
添加以下代码块以将Flask应用程序复制到容器中并安装必要的依赖项:
. . .
ADD ./requirements.txt /var/www/requirements.txt
RUN pip install -r requirements.txt
ADD . /var/www/
RUN pip install gunicorn
The following code will use the ADD
directive to copy files from the local app
directory to the /var/www
directory on the container. Next, the Dockerfile will use the RUN
directive to install Gunicorn and the packages specified in the requirements.txt
file, which you will create later in the tutorial.
以下代码将使用ADD
指令将文件从本地app
目录复制到容器上的/var/www
目录。 接下来,Dockerfile将使用RUN
指令安装Gunicorn以及在requirements.txt
文件中指定的软件包,您将在本教程的稍后部分中创建它们。
The following code block adds a new user and group and initializes the application:
以下代码块添加了新的用户和组,并初始化了应用程序:
. . .
RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh
USER www
EXPOSE 5000
CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]
By default, Docker containers run as the root user. The root user has access to everything in the system, so the implications of a security breach can be disastrous. To mitigate this security risk, this will create a new user and group that will only have access to the /var/www
directory.
默认情况下,Docker容器以root用户身份运行。 超级用户有权访问系统中的所有内容,因此安全漏洞的后果可能是灾难性的。 为了减轻这种安全风险,这将创建一个仅具有/var/www
目录访问权限的新用户和组。
This code will first use the addgroup
command to create a new group named www
. The -g
flag will set the group ID to the ENV GROUP_ID=1000
variable that is defined earlier in the Dockerfile
.
此代码将首先使用addgroup
命令创建一个名为www
的新组。 -g
标志会将组ID设置为Dockerfile
前面定义的ENV GROUP_ID=1000
变量。
The adduser -D -u $USER_ID -G www www -s /bin/sh
lines creates a www
user with a user ID of 1000
, as defined by the ENV
variable. The -s
flag creates the user’s home directory if it does not exist and sets the default login shell to /bin/sh
. The -G
flag is used to set the user’s initial login group to www
, which was created by the previous command.
所述adduser -D -u $USER_ID -G www www -s /bin/sh
线创建一个www
用户用的用户ID 1000
,由所定义的ENV
变量。 -s
标志创建用户的主目录(如果该目录不存在),并将默认的登录shell设置为/bin/sh
。 -G
标志用于将用户的初始登录组设置为www
,该组由上一个命令创建。
The USER
command defines that the programs run in the container will use the www
user. Gunicorn will listen on :5000
, so you will open this port with the EXPOSE
command.
USER
命令定义容器中运行的程序将使用www
用户。 Gunicorn将监听:5000
,因此您将使用EXPOSE
命令打开此端口。
Finally, the CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]
line runs the command to start the Gunicorn server with four workers listening on port 5000
. The number should generally be between 2–4 workers per core in the server, Gunicorn documentation recommends (2 x $num_cores) + 1
as the number of workers to start with.
最后, CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]
行运行命令来启动Gunicorn服务器,其中有四个工作人员正在侦听端口5000
。 该数量通常应在服务器中每个核心2到4个工人之间,Gunicorn文档建议(2 x $num_cores) + 1
作为开始的工人数量。
Your completed Dockerfile
will look like the following:
完成的Dockerfile
如下所示:
FROM python:3.6.8-alpine3.9
LABEL MAINTAINER="FirstName LastName "
ENV GROUP_ID=1000 \
USER_ID=1000
WORKDIR /var/www/
ADD . /var/www/
RUN pip install -r requirements.txt
RUN pip install gunicorn
RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh
USER www
EXPOSE 5000
CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]
Save the file and exit the text editor.
保存文件并退出文本编辑器。
Next, create a new directory to hold your Nginx configuration:
接下来,创建一个新目录来保存您的Nginx配置:
Then create the Dockerfile
for your Nginx web server in the nginx
directory:
然后在nginx
目录中为您的Nginx Web服务器创建Dockerfile
:
Add the following code to the file to create the Dockerfile that will build the image for your Nginx container:
将以下代码添加到文件中,以创建将为您的Nginx容器构建映像的Dockerfile:
FROM alpine:latest
LABEL MAINTAINER="FirstName LastName "
RUN apk --update add nginx && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log && \
mkdir /etc/nginx/sites-enabled/ && \
mkdir -p /run/nginx && \
rm -rf /etc/nginx/conf.d/default.conf && \
rm -rf /var/cache/apk/*
COPY conf.d/app.conf /etc/nginx/conf.d/app.conf
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
This Nginx Dockerfile
uses an alpine
base image, which is a tiny Linux distribution with a minimal attack surface built for security.
此Nginx Dockerfile
使用alpine
基础映像,这是一个小型Linux发行版,具有针对安全性构建的最小攻击面。
In the RUN
directive you are installing nginx
as well as creating symbolic links to publish the error and access logs to the standard error (/dev/stderr
) and output (/dev/stdout
). Publishing errors to standard error and output is a best practice since containers are ephemeral, doing this the logs are shipped to docker logs and from there you can forward your logs to a logging service like the Elastic stack for persistance. After this is done, commands are run to remove the default.conf
and /var/cache/apk/*
to reduce the size of the resulting image. Executing all of these commands in a single RUN
decreases the number of layers in the image, which also reduces the size of the resulting image.
在RUN
指令中,您将安装nginx
并创建符号链接以发布错误,并访问标准错误( /dev/stderr
)和输出( /dev/stdout
)的日志。 将错误发布到标准错误并输出是最佳实践,因为容器是临时的,这样做是将日志传送到docker日志,然后您可以从那里将日志转发到类似Elastic stack的日志服务以保持持久性。 完成此操作后,将运行命令以删除default.conf
和/var/cache/apk/*
以减小生成的图像的大小。 在单个RUN
执行所有这些命令会减少映像中的层数,这也会减小所得映像的大小。
The COPY
directive copies the app.conf
web server configuration inside of the container. The EXPOSE
directive ensures the containers listen on ports :80
and :443
, as your application will run on :80
with :443
as the secure port.
COPY
指令将app.conf
Web服务器配置复制到容器内部。 EXPOSE
指令确保容器在端口:80
和:443
,因为您的应用程序将在:80
以:443
作为安全端口运行。
Finally, the CMD
directive defines the command to start the Nginx server.
最后, CMD
指令定义了启动Nginx服务器的命令。
Save the file and exit the text editor.
保存文件并退出文本编辑器。
Now that the Dockerfile
is ready, you are ready to configure the Nginx reverse proxy to route traffic to the Flask application.
既然Dockerfile
已经准备好了,您就可以配置Nginx反向代理以将流量路由到Flask应用程序了。
In this step, you will configure Nginx as a reverse proxy to forward requests to Gunicorn on :5000
. A reverse proxy server is used to direct client requests to the appropriate back-end server. It provides an additional layer of abstraction and control to ensure the smooth flow of network traffic between clients and servers.
在此步骤中,您将Nginx配置为反向代理,以将请求转发到:5000
上的Gunicorn。 反向代理服务器用于将客户端请求定向到适当的后端服务器。 它提供了一个附加的抽象层和控制层,以确保客户端和服务器之间网络流量的顺畅流动。
Get started by creating the nginx/conf.d
directory:
通过创建nginx/conf.d
目录开始:
To configure Nginx, you need to create an app.conf
file with the following configuration in the nginx/conf.d/
folder. The app.conf
file contains the configuration that the reverse proxy needs to forward the requests to Gunicorn.
要配置Nginx,您需要在nginx/conf.d/
文件夹中使用以下配置创建一个app.conf
文件。 app.conf
文件包含反向代理将请求转发到Gunicorn所需的配置。
Put the following contents into the app.conf
file:
将以下内容放入app.conf
文件中:
upstream app_server {
server flask:5000;
}
server {
listen 80;
server_name _;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
client_max_body_size 64M;
location / {
try_files $uri @proxy_to_app;
}
location @proxy_to_app {
gzip_static on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_buffering off;
proxy_redirect off;
proxy_pass http://app_server;
}
}
This will first define the upstream server, which is commonly used to specify a web or app server for routing or load balancing.
这将首先定义上游服务器 ,通常用于指定用于路由或负载平衡的Web或应用程序服务器。
Your upstream server, app_server
, defines the server address with the server
directive, which is identified by the container name flask:5000
.
上游服务器app_server
使用server
指令定义服务器地址,该指令由容器名称flask:5000
标识。
The configuration for the Nginx web server is defined in the server
block. The listen
directive defines the port number on which your server will listen for incoming requests. The error_log
and access_log
directives define the files for writing logs. The proxy_pass
directive is used to set the upstream server for forwarding the requests to http://app_server
.
Nginx Web服务器的配置在server
块中定义。 listen
指令定义服务器将在其上侦听传入请求的端口号。 error_log
和access_log
伪指令定义用于写入日志的文件。 proxy_pass
指令用于设置上游服务器,以将请求转发到http:// app_server
。
Save and close the file.
保存并关闭文件。
With the Nginx web server configured, you can move on to creating the Flask to-do API.
配置Nginx Web服务器后,您可以继续创建Flask待办事项API。
Now that you’ve built out your environment, you’re ready to build your application. In this step, you will write a to-do API application that will save and display to-do notes sent in from a POST request.
既然您已经构建了环境,那么就可以构建应用程序了。 在此步骤中,您将编写一个待办事项API应用程序,该应用程序将保存并显示从POST请求发送来的待办事项注释。
Get started by creating the requirements.txt
file in the app
directory:
首先在app
目录中创建requirements.txt
文件:
This file is used to install the dependencies for your application. The implementation of this tutorial will use Flask
, Flask-PyMongo
, and requests
. Add the following to the requirements.txt
file:
此文件用于为您的应用程序安装依赖项。 本教程的实现将使用Flask
, Flask-PyMongo
和requests
。 将以下内容添加到requirements.txt
文件:
Flask==1.0.2
Flask-PyMongo==2.2.0
requests==2.20.1
Save the file and exit the editor after entering the requirements.
输入需求后,保存文件并退出编辑器。
Next, create the app.py
file to contain the Flask application code in the app
directory:
接下来,创建app.py
文件以在app
目录中包含Flask应用程序代码:
In your new app.py
file, enter in the code to import the dependencies:
在新的app.py
文件中,输入代码以导入依赖项:
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo
The os
package is used to import the environment variables. From the flask
library you imported the Flask
, request
, and jsonify
objects to instantiate the application, handle requests, and send JSON responses, respectively. From flask_pymongo
you imported the PyMongo
object to interact with the MongoDB.
os
包用于导入环境变量。 从flask
库导入的Flask
, request
和jsonify
对象实例化应用,处理请求和发送JSON响应,分别。 从flask_pymongo
您导入了PyMongo
对象以与MongoDB进行交互。
Next, add the code needed to connect to MongoDB:
接下来,添加连接到MongoDB所需的代码:
. . .
application = Flask(__name__)
application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']
mongo = PyMongo(application)
db = mongo.db
The Flask(__name__)
loads the application object into the application
variable. Next, the code builds the MongoDB connection string from the environment variables using os.environ
. Passing the application
object in to the PyMongo()
method will give you the mongo
object, which in turn gives you the db
object from mongo.db
.
Flask(__name__)
将应用程序对象加载到应用application
变量中。 接下来,代码使用os.environ
从环境变量构建MongoDB连接字符串。 将application
对象传递给PyMongo()
方法将为您提供mongo
对象,这又为您提供mongo.db
的db
对象。
Now you will add the code to create an index message:
现在,您将添加代码以创建索引消息:
. . .
@application.route('/')
def index():
return jsonify(
status=True,
message='Welcome to the Dockerized Flask MongoDB app!'
)
The @application.route('/')
defines the /
GET route of your API. Here your index()
function returns a JSON string using the jsonify
method.
@application.route('/')
定义您的API的/
GET路由。 在这里,您的index()
函数使用jsonify
方法返回JSON字符串。
Next, add the /todo
route to list all to-do’s:
接下来,添加/todo
路由以列出所有/todo
:
. . .
@application.route('/todo')
def todo():
_todos = db.todo.find()
item = {}
data = []
for todo in _todos:
item = {
'id': str(todo['_id']),
'todo': todo['todo']
}
data.append(item)
return jsonify(
status=True,
data=data
)
The @application.route('/todo')
defines the /todo
GET route of your API, which returns the to-dos in the database. The db.todo.find()
method returns all the to-dos in the database. Next, you iterate over the _todos
to build an item
that includes only the id
and todo
from the objects appending them to a data
array and finally returns them as JSON.
@application.route('/todo')
定义您的API的/todo
GET路由,该路由返回数据库中的待办事项。 db.todo.find()
方法返回数据库中的所有待办事项。 接下来,您遍历_todos
以构建仅包含id
和todo
的item
,这些对象将对象和id
追加到data
数组中,最后将它们作为JSON返回。
Next, add the code for creating the to-do:
接下来,添加用于创建待办事项的代码:
. . .
@application.route('/todo', methods=['POST'])
def createTodo():
data = request.get_json(force=True)
item = {
'todo': data['todo']
}
db.todo.insert_one(item)
return jsonify(
status=True,
message='To-do saved successfully!'
), 201
The @application.route('/todo')
defines the /todo
POST route of your API, which creates a to-do note in the database. The request.get_json(force=True)
gets the JSON that you post to the route, and item
is used to build the JSON that will be saved in the to-do. The db.todo.insert_one(item)
is used to insert one item into the database. After the to-do is saved in the database you return a JSON response with a status code of 201 CREATED
.
@application.route('/todo')
定义了API的/todo
POST路由,该路由在数据库中创建了一个/todo
。 request.get_json(force=True)
获取您发布到路线的JSON,并且item
用于构建将保存在待办事项中的JSON。 db.todo.insert_one(item)
用于将一项插入数据库。 将待办事项保存在数据库中后,您将返回状态码为201 CREATED
的JSON响应。
Now you add the code to run the application:
现在,添加代码以运行应用程序:
. . .
if __name__ == "__main__":
ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)
The condition __name__ == "__main__"
is used to check if the global variable, __name__
, in the module is the entry point to your program, is "__main__"
, then run the application. If the __name__
is equal to "__main__"
then the code inside the if
block will execute the app using this command application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)
.
条件__name__ == "__main__"
用于检查__name__ == "__main__"
的全局变量__name__
是否是程序的入口点"__main__"
,然后运行该应用程序。 如果__name__
等于"__main__"
则if
块中的代码将使用以下命令application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)
执行该应用application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)
。
Next, we get the values for the ENVIRONMENT_DEBUG
and ENVIRONMENT_PORT
from the environment variables using os.environ.get()
, using the key as the first parameter and default value as the second parameter. The application.run()
sets the host
, port
, and debug
values for the application.
接下来,我们使用os.environ.get()
从环境变量中获取ENVIRONMENT_DEBUG
和ENVIRONMENT_PORT
的值,并将键作为第一个参数,将默认值用作第二个参数。 application.run()
设置application.run()
的host
, port
和debug
值。
The completed app.py
file will look like this:
完整的app.py
文件将如下所示:
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo
application = Flask(__name__)
application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']
mongo = PyMongo(application)
db = mongo.db
@application.route('/')
def index():
return jsonify(
status=True,
message='Welcome to the Dockerized Flask MongoDB app!'
)
@application.route('/todo')
def todo():
_todos = db.todo.find()
item = {}
data = []
for todo in _todos:
item = {
'id': str(todo['_id']),
'todo': todo['todo']
}
data.append(item)
return jsonify(
status=True,
data=data
)
@application.route('/todo', methods=['POST'])
def createTodo():
data = request.get_json(force=True)
item = {
'todo': data['todo']
}
db.todo.insert_one(item)
return jsonify(
status=True,
message='To-do saved successfully!'
), 201
if __name__ == "__main__":
ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)
Save the file and exit the editor.
保存文件并退出编辑器。
Next, create the wsgi.py
file in the app
directory.
接下来,在app
目录中创建wsgi.py
文件。
The wsgi.py
file creates an application object (or callable) so that the server can use it. Each time a request comes, the server uses this application object to run the application’s request handlers upon parsing the URL.
wsgi.py
文件创建一个应用程序对象(或可调用),以便服务器可以使用它。 每次请求到来时,服务器都会在解析URL时使用此应用程序对象来运行应用程序的请求处理程序。
Put the following contents into the wsgi.py
file, save the file, and exit the text editor:
将以下内容放入wsgi.py
文件中,保存文件,然后退出文本编辑器:
from app import application
if __name__ == "__main__":
application.run()
This wsgi.py
file imports the application object from the app.py
file and creates an application object for the Gunicorn server.
此wsgi.py
文件从wsgi.py
文件导入应用程序对象,并为app.py
服务器创建应用程序对象。
The to-do app is now in place, so you’re ready to start running the application in containers.
待办事项应用程序现已就绪,因此您可以开始在容器中运行该应用程序。
Now that you have defined all of the services in your docker-compose.yml
file and their configurations, you can start the containers.
现在,您已经在docker-compose.yml
文件及其配置中定义了所有服务,您可以启动容器。
Since the services are defined in a single file, you need to issue a single command to start the containers, create the volumes, and set up the networks. This command also builds the image for your Flask application and the Nginx web server. Run the following command to build the containers:
由于服务是在单个文件中定义的,因此您需要发出单个命令来启动容器,创建卷和设置网络。 此命令还将为Flask应用程序和Nginx Web服务器生成映像。 运行以下命令来构建容器:
When running the command for the first time, it will download all of the necessary Docker images, which can take some time. Once the images are downloaded and stored in your local machine, docker-compose
will create your containers. The -d
flag daemonizes the process, which allows it to run as a background process.
首次运行该命令时,它将下载所有必要的Docker映像,这可能需要一些时间。 将映像下载并存储在本地计算机中后, docker-compose
将创建您的容器。 -d
标志守护进程,使其可以作为后台进程运行。
Use the following command to list the running containers once the build process is complete:
构建过程完成后,使用以下命令列出正在运行的容器:
You will see output similar to the following:
您将看到类似于以下内容的输出:
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f20e9a7fd2b9 digitalocean.com/webserver:latest "nginx -g 'daemon of…" 2 weeks ago Up 2 weeks 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp webserver
3d53ea054517 digitalocean.com/flask-python:3.6 "gunicorn -w 4 --bin…" 2 weeks ago Up 2 weeks 5000/tcp flask
96f5a91fc0db mongo:4.0.8 "docker-entrypoint.s…" 2 weeks ago Up 2 weeks 27017/tcp mongodb
The CONTAINER ID
is a unique identifier that is used to access containers. The IMAGE
defines the image name for the given container. The NAMES
field is the service name under which containers are created, similar to CONTAINER ID
these can be used to access containers. Finally, the STATUS
provides information regarding the state of the container whether it’s running, restarting, or stopped.
CONTAINER ID
是用于访问容器的唯一标识符。 IMAGE
定义给定容器的图像名称。 NAMES
字段是用来创建容器的服务名称,类似于CONTAINER ID
它们可用于访问容器。 最后, STATUS
提供有关容器状态的信息,无论容器处于运行,重新启动还是停止状态。
You’ve used the docker-compose
command to build your containers from your configuration files. In the next step, you will create a MongoDB user for your application.
您已使用docker-compose
命令从配置文件中构建容器。 在下一步中,您将为您的应用程序创建一个MongoDB用户。
By default, MongoDB allows users to log in without credentials and grants unlimited privileges. In this step, you will secure your MongoDB database by creating a dedicated user to access it.
默认情况下,MongoDB允许用户不使用凭据登录并授予无限权限。 在此步骤中,您将通过创建专用用户来访问数据库来保护MongoDB数据库。
To do this, you will need the root username and password that you set in the docker-compose.yml
file environment variables MONGO_INITDB_ROOT_USERNAME
and MONGO_INITDB_ROOT_PASSWORD
for the mongodb
service. In general, it’s better to avoid using the root administrative account when interacting with the database. Instead, you will create a dedicated database user for your Flask application, as well as a new database that the Flask app will be allowed to access.
为此,您将需要在mongodb
服务的MONGO_INITDB_ROOT_PASSWORD
docker-compose.yml
文件环境变量MONGO_INITDB_ROOT_USERNAME
和MONGO_INITDB_ROOT_PASSWORD
设置的根用户名和密码。 通常,最好在与数据库进行交互时避免使用根管理帐户。 相反,您将为Flask应用程序创建一个专用的数据库用户,以及一个允许Flask应用程序访问的新数据库。
To create a new user, first start an interactive shell on the mongodb
container:
要创建新用户,请首先在mongodb
容器上启动一个交互式shell:
docker exec -it mongodb bash
docker exec -it mongodb bash
You use the docker exec
command in order to run a command inside a running container along with the -it
flag to run an interactive shell inside the container.
您可以使用docker exec
命令在运行中的容器内运行命令,并使用-it
标志在容器内运行交互式shell。
Once inside the container, log in to the MongoDB root administrative account:
进入容器后,登录到MongoDB 根管理帐户:
mongo -u mongodbuser -p
mongo -u mongodbuser -p
You will be prompted for the password that you entered as the value for the MONGO_INITDB_ROOT_PASSWORD
variable in the docker-compose.yml
file. The password can be changed by setting a new value for the MONGO_INITDB_ROOT_PASSWORD
in the mongodb
service, in which case you will have to re-run the docker-compose up -d
command.
在MONGO_INITDB_ROOT_PASSWORD
docker-compose.yml
文件中,系统将提示您输入输入的密码作为MONGO_INITDB_ROOT_PASSWORD
变量的值。 可以通过在mongodb
服务中为MONGO_INITDB_ROOT_PASSWORD
设置新值来更改密码,在这种情况下,您将不得不重新运行docker-compose up -d
命令。
Run the show dbs;
command to list all databases:
运行show dbs;
列出所有数据库的命令:
You will see the following output:
您将看到以下输出:
Output
admin 0.000GB
config 0.000GB
local 0.000GB
5 rows in set (0.00 sec)
The admin
database is a special database that grants administrative permissions to users. If a user has read access to the admin
database, they will have read and write permissions to all other databases. Since the output lists the admin
database, the user has access to this database and can therefore read and write to all other databases.
admin
数据库是一个特殊的数据库,它向用户授予管理权限。 如果用户具有对admin
数据库的读取访问权限,则他们将具有对所有其他数据库的读写权限。 由于输出列出了admin
数据库,因此用户可以访问此数据库,因此可以读取和写入所有其他数据库。
Saving the first to-do note will automatically create the MongoDB database. MongoDB allows you to switch to a database that does not exist using the use database
command. It creates a database when a document is saved to a collection. Therefore the database is not created here; that will happen when you save your first to-do note in the database from the API. Execute the use
command to switch to the flaskdb
database:
保存第一个待办事项便会自动创建MongoDB数据库 。 MongoDB允许您使用use database
命令切换到不存在的use database
。 将文档保存到集合时,它将创建一个数据库。 因此,此处未创建数据库。 当您通过API将第一个待办事项保存在数据库中时,就会发生这种情况。 执行use
命令切换到flaskdb
数据库:
Next, create a new user that will be allowed to access this database:
接下来,创建一个新用户,该用户将被允许访问此数据库:
db.createUser({user: 'flaskuser', pwd: 'your password', roles: [{role: 'readWrite', db: 'flaskdb'}]})
db.createUser({user:' flaskuser ',pwd:' your password ',角色:[{role:'readWrite',db:' flaskdb '}]})
This command creates a user named flaskuser with readWrite
access to the flaskdb
database. Be sure to use a secure password in the pwd
field. The user
and pwd
here are the values you defined in the docker-compose.yml
file under the environment variables section for the flask
service.
此命令创建一个名为flaskuser的用户,该用户具有对flaskdb
数据库的readWrite
访问权限。 确保在pwd
字段中使用安全密码。 这里的user
和pwd
是您在flask
服务的环境变量部分下的docker-compose.yml
文件中定义的值。
Log in to the authenticated database with the following command:
使用以下命令登录到经过身份验证的数据库:
mongo -u flaskuser -p your password --authenticationDatabase flaskdb
mongo -u flaskuser -p 您的密码 --authentication数据库flaskdb
Now that you have added the user, log out of the database.
现在您已经添加了用户,请注销数据库。
And finally, exit the container:
最后,退出容器:
You’ve now configured a dedicated database and user account for your Flask application. The database components are ready, so now you can move on to running the Flask to-do app.
现在,您已经为Flask应用程序配置了专用的数据库和用户帐户。 数据库组件已准备就绪,因此现在您可以继续运行Flask待办事项应用程序。
Now that your services are configured and running, you can test your application by navigating to http://your_server_ip
in a browser. Additionally, you can run curl
to see the JSON response from Flask:
现在您的服务已配置并正在运行,您可以通过在浏览器中导航到http:// your_server_ip
来测试您的应用程序。 此外,您可以运行curl
来查看Flask的JSON响应:
curl -i http://your_server_ip
curl -i http:// your_server_ip
You will receive the following response:
您将收到以下答复:
Output
{"message":"Welcome to the Dockerized Flask MongoDB app!","status":true}
The configuration for the Flask application is passed to the application from the docker-compose.yml
file. The configuration regarding the database connection is set using the MONGODB_*
variables defined in the environment
section of the flask
service.
Flask应用程序的配置从docker-compose.yml
文件传递到应用程序。 使用在flask
服务的environment
部分中定义的MONGODB_*
变量来设置有关数据库连接的配置。
To test everything out, create a to-do note using the Flask API. You can do this with a POST
curl request to the /todo
route:
要测试所有内容,请使用Flask API创建待办事项。 您可以使用对/todo
路由的POST
curl请求来执行此操作:
curl -i -H "Content-Type: application/json" -X POST -d '{"todo": "Dockerize Flask application with MongoDB backend"}' http://your_server_ip/todo
curl -i -H“内容类型:application / json” -X POST -d'{“ todo”:“使用MongoDB后端对Docker Flask应用程序进行打包”}}'http:// your_server_ip / todo
This request results in a response with a status code of 201 CREATED
when the to-do item is saved to MongoDB:
当待办事项保存到MongoDB时,此请求将导致状态码为201 CREATED
的响应:
Output
{"message":"To-do saved successfully!","status":true}
You can list all of the to-do notes from MongoDB with a GET request to the /todo
route:
您可以通过对/todo
路由的GET请求列出MongoDB中的所有/todo
:
curl -i http://your_server_ip/todo
curl -i http://您的服务器_ip / todo
Output
{"data":[{"id":"5c9fa25591cb7b000a180b60","todo":"Dockerize Flask application with MongoDB backend"}],"status":true}
With this, you have Dockerized a Flask API running a MongoDB backend with Nginx as a reverse proxy deployed to your servers. For a production environment you can use sudo systemctl enable docker
to ensure your Docker service automatically starts at runtime.
这样,您就可以对运行MongoDB后端的Flask API进行Docker化,并将Nginx作为部署到服务器的反向代理。 对于生产环境,您可以使用sudo systemctl enable docker
来确保您的Docker服务在运行时自动启动。
In this tutorial, you deployed a Flask application with Docker, MongoDB, Nginx, and Gunicorn. You now have a functioning modern stateless API application that can be scaled. Although you can achieve this result by using a command like docker container run
, the docker-compose.yml
simplifies your job as this stack can be put into version control and updated as necessary.
在本教程中,您使用Docker,MongoDB,Nginx和Gunicorn部署了Flask应用程序。 现在,您可以正常运行的无状态API应用程序可以进行缩放。 尽管您可以通过使用诸如docker-compose.yml
docker container run
类的命令来实现此结果,但docker-compose.yml
简化您的工作,因为可以将该堆栈放入版本控制中并根据需要进行更新。
From here you can also take a look at our further Python Framework tutorials.
从这里您还可以查看我们进一步的Python Framework教程 。
翻译自: https://www.digitalocean.com/community/tutorials/how-to-set-up-flask-with-mongodb-and-docker