翻译自:《Dockerizing Flask with Postgres, Gunicorn, and Nginx》

这是一个分步教程,详细介绍了如何配置 Flask 在 Postgres 上运行于 Docker。 对于生产环境,我们将添加 Nginx 和Gunicorn。 我们还将研究如何通过 Nginx 提供静态和用户上传的媒体文件。

主要依赖:

  1. Flask v1.1.1
  2. Docker v19.03.4
  3. Python v3.8.2

建立项目

  1. $ mkdir flask-on-docker && cd flask-on-docker
  2. $ mkdir services && cd services
  3. $ mkdir web && cd web
  4. $ mkdir project
  5. $ python -m venv env
  6. $ source env/bin/activate
  7. (env)$ pip install flask

创建 Flask 应用:

  1. # ./services/web/project/__init__.py
  2. from flask import Flask, jsonify
  3. app = Flask(__name__)
  4. @app.route("/")
  5. def hello_world():
  6. return jsonify(hello="world")

然后,要配置Flask CLI工具以从命令行运行和管理应用程序,请将 manage.py 文件添加到 web 目录中:

  1. # ./services/web/manage.py
  2. from flask.cli import FlaskGroup
  3. from project import app
  4. cli = FlaskGroup(app)
  5. if __name__ == "__main__":
  6. cli()

在这里,我们创建了一个新的 FlaskGroup 实例,以使用与Flask应用相关的命令扩展普通的 CLI。

web 目录运行服务器:

  1. (env)$ export FLASK_APP=project/__init__.py
  2. (env)$ python manage.py run

导航到 http://localhost:5000/ 。您应该看到:

  1. {
  2. "hello": "world"
  3. }

web 目录中创建一个 requirements.txt 文件,并将Flask添加为依赖项:

  1. Flask==1.1.1

您的项目结构应该是这样的:

└── services
    └── web
        ├── manage.py
        ├── project
        │   └── __init__.py
        └── requirements.txt

Docker

安装 Docker,如果你还没有它,那么添加一个Dockerfile到 web 目录:

# pull official base image
FROM python:3.8-slim-buster

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt

# copy project
COPY . /usr/src/app/

接下来,将 docker-compose.yml 文件添加到项目根目录:


services:
  web:
    build: ./services/web
    command: python manage.py run -h 0.0.0.0
    volumes:
      - ./services/web/:/usr/src/app/
    ports:
      - 5000:5000
    env_file:
      - ./.env.dev

然后,在项目根目录中创建一个 .env.dev 文件,以存储用于开发的环境变量:

FLASK_APP=project/__init__.py
FLASK_ENV=development

构建镜像:

$ docker-compose build

生成映像后,运行容器:

$ docker-compose up -d

导航到 http://localhost:5000/ 再次看到相同结果。

如果不工作,可以通过 docker-compose logs -f 检查日志中的错误。

Postgres

要配置 Postgres,我们需要向 docker-compose.yml 文件中添加新服务,设置 flask-sqlalchemy ,然后安装 Psycopg2

首先,将一个名为 db 的新服务添加到 docker-compose.yml 中:

version: '3.7'

services:
  web:
    build: ./services/web
    command: python manage.py run -h 0.0.0.0
    volumes:
      - ./services/web/:/usr/src/app/
    ports:
      - 5000:5000
    env_file:
      - ./.env.dev
    depends_on:
      - db
  db:
    image: postgres:12-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=hello_flask
      - POSTGRES_PASSWORD=hello_flask
      - POSTGRES_DB=hello_flask_dev

volumes:
  postgres_data:

为了在容器的使用生命周期之外保留数据,我们配置了一个数据卷。 此配置会将 postgres_data 绑定到容器中的 /var/lib/postgresql/data/ 目录。

我们还添加了一个环境密钥来定义默认数据库的名称,并设置用户名和密码。

查看 Postgres Docker Hub page 页面的“环境变量”部分以获得更多信息。

然后将 DATABASE_URL 环境变量添加到 .env.dev

FLASK_APP=project/__init__.py
FLASK_ENV=development
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev

然后,将一个名为 config.py 的新文件添加到 project 目录,在该目录中,我们将定义特定于环境的配置变量:

import os

basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite://")
    SQLALCHEMY_TRACK_MODIFICATIONS = False

在这里,数据库是根据我们刚刚定义的 DATABASE_URL 环境变量配置的。 注意默认值。

更新 __init__.py 以获取配置:

from flask import Flask, jsonify

app = Flask(__name__)
app.config.from_object("project.config.Config")

@app.route("/")
def hello_world():
    return jsonify(hello="world")

添加 Flask-SQLAlchemy and Psycopg2 依赖到 requirements.txt 文件:

Flask==1.1.1
Flask-SQLAlchemy==2.4.1
psycopg2-binary==2.8.4

再次更新 __init__.py 文件,以创建一个新的 SQLAlchemy 实例并定义一个数据库模型:

from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
app.config.from_object("project.config.Config")
db = SQLAlchemy(app)


class User(db.Model):
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(128), unique=True, nullable=False)
    active = db.Column(db.Boolean(), default=True, nullable=False)

    def __init__(self, email):
        self.email = email


@app.route("/")
def hello_world():
    return jsonify(hello="world")

最后更新 manage.py 文件:

from flask.cli import FlaskGroup

from project import app, db


cli = FlaskGroup(app)


@cli.command("create_db")
def create_db():
    db.drop_all()
    db.create_all()
    db.session.commit()


if __name__ == "__main__":
    cli()

这会将一个新命令 create_db 注册到 CLI,以便我们可以从命令行运行它,稍后将使用该模型将模型应用于数据库。构建新镜像:

$ docker-compose up -d --build

创建数据库:

$ docker-compose exec web python manage.py create_db

确保创建了 users 表:

$ docker-compose exec db psql --username=hello_flask --dbname=hello_flask_dev

psql (12.2)
Type "help" for help.

hello_flask_dev=# \l
                                        List of databases
      Name       |    Owner    | Encoding |  Collate   |   Ctype    |      Access privileges
-----------------+-------------+----------+------------+------------+-----------------------------
 hello_flask_dev | hello_flask | UTF8     | en_US.utf8 | en_US.utf8 |
 postgres        | hello_flask | UTF8     | en_US.utf8 | en_US.utf8 |
 template0       | hello_flask | UTF8     | en_US.utf8 | en_US.utf8 | =c/hello_flask             +
                 |             |          |            |            | hello_flask=CTc/hello_flask
 template1       | hello_flask | UTF8     | en_US.utf8 | en_US.utf8 | =c/hello_flask             +
                 |             |          |            |            | hello_flask=CTc/hello_flask
(4 rows)

hello_flask_dev=# \c hello_flask_dev
You are now connected to database "hello_flask_dev" as user "hello_flask".

hello_flask_dev=# \dt
          List of relations
 Schema | Name  | Type  |    Owner
--------+-------+-------+-------------
 public | users | table | hello_flask
(1 row)

hello_flask_dev=# \q

您可以通过运行以下命令检查该数据卷是否也已创建:

$ docker volume inspect flask-on-docker_postgres_data

您应该看到类似以下内容:

[
    {
        "CreatedAt": "2020-02-24T13:39:47Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "flask-on-docker",
            "com.docker.compose.version": "1.24.1",
            "com.docker.compose.volume": "postgres_data"
        },
        "Mountpoint": "/var/lib/docker/volumes/flask-on-docker_postgres_data/_data",
        "Name": "flask-on-docker_postgres_data",
        "Options": null,
        "Scope": "local"
    }
]

接下来,在创建数据库表和运行 Flask 开发服务器之前,将 entrypoint.sh 文件添加到 web 目录以验证Postgres 是否正常运行:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi

python manage.py create_db

exec "$@"

注意环境变量。然后在本地更新文件权限:

$ chmod +x services/web/entrypoint.sh

然后,更新 Dockerfile 以安装 Netcat,复制 entrypoint.sh 文件,然后将该文件作为 Docker entrypoint命令运行:

# pull official base image
FROM python:3.8.1-slim-buster

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install system dependencies
RUN apt-get update && apt-get install -y netcat

# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt

# copy project
COPY . /usr/src/app/

# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

entrypoint.sh 脚本的 SQL_HOSTSQL_PORTDATABASE 环境变量添加到 .env.dev

FLASK_APP=project/__init__.py
FLASK_ENV=development
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres

测试一下:

  1. 重建图像
  2. 运行容器
  3. 打开 http://localhost:5000/

我们还为 CLI 添加一个 seed_db 命令,用于将示例用户添加到 manage.py 中的 users 表中:

from flask.cli import FlaskGroup

from project import app, db, User


cli = FlaskGroup(app)


@cli.command("create_db")
def create_db():
    db.drop_all()
    db.create_all()
    db.session.commit()


@cli.command("seed_db")
def seed_db():
    db.session.add(User(email="michael@mherman.org"))
    db.session.commit()


if __name__ == "__main__":
    cli()

尝试运行:

$ docker-compose exec web python manage.py seed_db

$ docker-compose exec db psql --username=hello_flask --dbname=hello_flask_dev

psql (12.0)
Type "help" for help.

hello_flask_dev=# \c hello_flask_dev
You are now connected to database "hello_flask_dev" as user "hello_flask".

hello_flask_dev=# select * from users;
 id |        email        | active
----+---------------------+--------
  1 | michael@mherman.org | t
(1 row)

hello_flask_dev=# \q

尽管添加了 Postgres,我们仍然可以通过不设置 DATABASE_URL 环境变量来为 Flask 创建一个独立的Docker映像。 要进行测试,请生成一个新映像,然后运行一个新容器:

$ docker build -f ./services/web/Dockerfile -t hello_flask:latest ./services/web
$ docker run -p 5001:5000 \
    -e "FLASK_APP=project/__init__.py" -e "FLASK_ENV=development" \
    hello_flask python /usr/src/app/manage.py run -h 0.0.0.0

您应该能够在 http://localhost:5001 上查看 hello world 健全性检查。

Gunicorn

在生产环境中,让我们将 Gunicorn(生产级别 WSGI 服务器)添加到需求文件中:

Flask==1.1.1
Flask-SQLAlchemy==2.4.1
gunicorn==20.0.4
psycopg2-binary==2.8.4

由于我们仍要在开发中使用 Flask 的内置服务器,因此创建一个名为 docker-compose.prod.yml 的新撰写文件进行生产:

version: '3.7'

services:
  web:
    build: ./services/web
    command: gunicorn --bind 0.0.0.0:5000 manage:app
    ports:
      - 5000:5000
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:12-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db

volumes:
  postgres_data:

如果您有多个环境,则可能需要使用 docker-compose.override.yml 配置文件。 使用这种方法,您可以将基本配置添加到 docker-compose.yml 文件,然后使用 docker-compose.override.yml 文件根据环境覆盖这些配置设置。

注意默认命令。 我们正在运行 Gunicorn,而不是 Flask 开发服务器。 我们还从Web 服务中删除了该卷,因为在生产中不需要它。 最后,我们使用单独的环境变量文件来定义两个服务的环境变量,这些环境变量将在运行时传递给容器。

# .env.prod
FLASK_APP=project/__init__.py
FLASK_ENV=production
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_prod
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
# .env.prod.db
POSTGRES_USER=hello_flask
POSTGRES_PASSWORD=hello_flask
POSTGRES_DB=hello_flask_prod

将两个文件添加到项目根目录。 您可能想让它们脱离版本控制,因此将它们添加到 .gitignore 文件中。运行命令:

$ docker-compose down -v
$ docker-compose -f docker-compose.prod.yml up -d --build

验证是否已与 users 表一起创建了 hello_flask_prod 数据库。 测试 http://localhost:5000/

生产环境

您是否注意到我们仍在运行 create_db 命令,该命令会在每次运行容器时删除所有现有表,然后从模型中创建表? 这在开发中很好,但是让我们为生产创建一个新的入口点文件。
文件 entrypoint.prod.sh:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi

exec "$@"

或者,您可以像这样更改现有的文件,而不是创建新的入口点文件:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
      sleep 0.1
    done

    echo "PostgreSQL started"
fi

if [ "$FLASK_ENV" = "development" ]
then
    echo "Creating the database tables..."
    python manage.py create_db
    echo "Tables created"
fi

exec "$@"

在本地更新文件权限:

$ chmod +x services/web/entrypoint.prod.sh

要使用此文件,请创建一个名为 Dockerfile.prod 的新 Dockerfile 以用于生产版本:

###########
# BUILDER #
###########

# pull official base image
FROM python:3.8.1-slim-buster as builder

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install system dependencies
RUN apt-get update && \
    apt-get install -y --no-install-recommends gcc

# lint
RUN pip install --upgrade pip
RUN pip install flake8
COPY . /usr/src/app/
RUN flake8 --ignore=E501,F401 .

# install python dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt


#########
# FINAL #
#########

# pull official base image
FROM python:3.8.1-slim-buster

# create directory for the app user
RUN mkdir -p /home/app

# create the app user
RUN addgroup -S app && adduser -S app -G app

# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

# install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends netcat
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --upgrade pip
RUN pip install --no-cache /wheels/*

# copy entrypoint-prod.sh
COPY ./entrypoint.prod.sh $APP_HOME

# copy project
COPY . $APP_HOME

# chown all the files to the app user
RUN chown -R app:app $APP_HOME

# change to the app user
USER app

# run entrypoint.prod.sh
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]

在这里,我们使用了 Docker 多阶段构建来减小最终映像的大小。 本质上, builder 是用于构建 Python Wheel 文件的临时映像。 然后将构建生成的 Wheel 文件复制到最终生产镜像中,并丢弃builder 镜像。

您可以进一步采用多阶段构建方法,并使用单个 Dockerfile 而不是创建两个 Dockerfile 文件。 考虑在两个不同的文件上使用此方法的利弊。

您是否注意到我们创建了非 root 用户? 默认情况下,Docker 作为容器内部的根运行容器进程。 这是一个不好的做法,因为如果攻击者设法突破容器,他们可以获得对Docker主机的根访问权限。 如果您是容器的 root 用户,那么您将是主机的root用户。
更新 docker-compose.prod.yml 文件中的 web 服务以使用 Dockerfile.prod 进行构建:

web:
  build:
    context: ./services/web
    dockerfile: Dockerfile.prod
  command: gunicorn --bind 0.0.0.0:5000 manage:app
  ports:
    - 5000:5000
  env_file:
    - ./.env.prod
  depends_on:
    - db

尝试运行一下命令:

$ docker-compose -f docker-compose.prod.yml down -v
$ docker-compose -f docker-compose.prod.yml up -d --build
$ docker-compose -f docker-compose.prod.yml exec web python manage.py create_db

Nginx

接下来,让我们将 Nginx 添加到组合中,以充当Gunicorn的反向代理,以处理客户端请求以及提供静态文件。
将服务添加到 docker-compose.prod.yml 中:

nginx:
  build: ./services/nginx
  ports:
    - 1337:80
  depends_on:
    - web

然后,在“services”目录中,创建以下文件和文件夹:

└── nginx
    ├── Dockerfile
    └── nginx.conf

Dockerfile:

FROM nginx:1.17-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

nginx.conf:

upstream hello_flask {
    server web:5000;
}

server {

    listen 80;

    location / {
        proxy_pass http://hello_flask;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

}

有关如何配置Nginx与Flask一起使用的更多信息,请参见如何为Flask Web应用程序配置NGINX。

然后,在 docker-compose.prod.yml 中更新 web 服务,将 ports 替换为 expose

web:
  build:
    context: ./services/web
    dockerfile: Dockerfile.prod
  command: gunicorn --bind 0.0.0.0:5000 manage:app
  expose:
    - 5000
  env_file:
    - ./.env.prod
  depends_on:
    - db

现在,端口 5000 仅在内部公开给其他 Docker 服务。 该端口将不再发布到主机。

有关 portsexpose 的更多信息,请查看 Stack Overflow 问题

再次测试:

$ docker-compose -f docker-compose.prod.yml down -v
$ docker-compose -f docker-compose.prod.yml up -d --build
$ docker-compose -f docker-compose.prod.yml exec web python manage.py create_db

确保该应用程序已启动并在http://localhost:1337上运行。您的项目结构现在应如下所示:

├── .env.dev
├── .env.prod
├── .env.prod.db
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
└── services
    ├── nginx
    │   ├── Dockerfile
    │   └── nginx.conf
    └── web
        ├── Dockerfile
        ├── Dockerfile.prod
        ├── entrypoint.prod.sh
        ├── entrypoint.sh
        ├── manage.py
        ├── project
        │   ├── __init__.py
        │   └── config.py
        └── requirements.txt

完成后将容器删除:

$ docker-compose -f docker-compose.prod.yml down -v

由于 Gunicorn 是应用程序服务器,所以它将不提供静态文件。 因此,在此特定配置中应如何处理静态文件和媒体文件?

静态文件

首先在 services/web/project 文件夹中创建以下文件和文件夹:

└── static
    └── hello.txt

添加一些文本到 hello.txt

hi!

__init__.py 添加新的路由处理程序:

from flask import Flask, jsonify, send_from_directory

@app.route("/static/<path:filename>")
def staticfiles(filename):
    return send_from_directory(app.config["STATIC_FOLDER"], filename)

不要忘记导入send_from_directory。最后, 添加 STATIC_FOLDER 配置到 services/web/project/config.py 文件:

import os

basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite://")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    STATIC_FOLDER = f"{os.getenv('APP_FOLDER')}/project/static"

开发环境

APP_FOLDER 环境变量添加到 .env.dev

FLASK_APP=project/__init__.py
FLASK_ENV=development
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
APP_FOLDER=/usr/src/app

要进行测试,请先重新构建图像,然后按常规创建新容器。 完成后确保http://localhost:5000/static/hello.txt 正确提供了文件

生产环境

为了进行生产,请在 docker-compose.prod.yml 中向 webnginx 服务添加一个数据卷,以便每个容器将共享一个名为 static 的目录:

version: '3.7'

services:
  web:
    build:
      context: ./services/web
      dockerfile: Dockerfile.prod
    command: gunicorn --bind 0.0.0.0:5000 manage:app
    volumes:
      - static_volume:/home/app/web/project/static
    expose:
      - 5000
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:12-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db
  nginx:
    build: ./services/nginx
    volumes:
      - static_volume:/home/app/web/project/static
    ports:
      - 1337:80
    depends_on:
      - web

volumes:
  postgres_data:
  static_volume:

接下来,更新Nginx配置以将静态文件请求路由到 static 文件夹:

upstream hello_flask {
    server web:5000;
}

server {

    listen 80;

    location / {
        proxy_pass http://hello_flask;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
        alias /home/app/web/project/static/;
    }

}

APP_FOLDER 环境变量添加到 .env.prod 文件:

FLASK_APP=project/__init__.py
FLASK_ENV=production
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_prod
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
APP_FOLDER=/home/app/web

然后删除旧容器并重新创建和运行容器:

$ docker-compose down -v
$ docker-compose -f docker-compose.prod.yml up -d --build

再次,请求 http://localhost:1337/static/* 服务将由 static 目录提供。

打开 http://localhost:1337/static/hello.txt,并确保正确加载了静态文件。
您还可以在日志中通过 docker-compose -f docker-compose.prod.ymllogs -f 验证是否已通过Nginx成功处理了对静态文件的请求:

nginx_1  | 172.23.0.1 - - [24/Feb/2020:17:01:24 +0000] "GET /static/hello.txt HTTP/1.1" 200 4 "-"
           "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:73.0) Gecko/20100101 Firefox/73.0" "-"

完成后删除容器:

$ docker-compose -f docker-compose.prod.yml down -v

媒体文件

要测试对用户上传的媒体文件的处理,请在 __init__.py 中添加两个新的路由处理程序:

@app.route("/media/<path:filename>")
def mediafiles(filename):
    return send_from_directory(app.config["MEDIA_FOLDER"], filename)


@app.route("/upload", methods=["GET", "POST"])
def upload_file():
    if request.method == "POST":
        file = request.files["file"]
        filename = secure_filename(file.filename)
        file.save(os.path.join(app.config["MEDIA_FOLDER"], filename))
    return f"""
    <!doctype html>
    <title>upload new File</title>
    <form action="" method=post enctype=multipart/form-data>
      <p><input type=file name=file><input type=submit value=Upload>
    </form>
    """

同时更新导入:

import os

from werkzeug.utils import secure_filename
from flask import (
    Flask,
    jsonify,
    send_from_directory,
    request,
    redirect,
    url_for
)
from flask_sqlalchemy import SQLAlchemy

添加 MEDIA_FOLDER 到配置文件 services/web/project/config.py 中:

import os


basedir = os.path.abspath(os.path.dirname(__file__))


class Config(object):
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite://")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    STATIC_FOLDER = f"{os.getenv('APP_FOLDER')}/project/static"
    MEDIA_FOLDER = f"{os.getenv('APP_FOLDER')}/project/media"

最后,在“project”文件夹中创建一个名为“media”的新文件夹。

开发环境

测试:

$ docker-compose up -d --build

您应该能够在 http://localhost:5000/upload 上载图像,然后在 http://localhost:5000/media/IMAGE_FILE_NAME上查看图像。

生产环境

对于生产,将另一个数据卷添加到 webnginx 服务:

version: '3.7'

services:
  web:
    build:
      context: ./services/web
      dockerfile: Dockerfile.prod
    command: gunicorn --bind 0.0.0.0:5000 manage:app
    volumes:
      - static_volume:/home/app/web/project/static
      - media_volume:/home/app/web/project/media
    expose:
      - 5000
    env_file:
      - ./.env.prod
    depends_on:
      - db
  db:
    image: postgres:12-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - ./.env.prod.db
  nginx:
    build: ./services/nginx
    volumes:
      - static_volume:/home/app/web/project/static
      - media_volume:/home/app/web/project/media
    ports:
      - 1337:80
    depends_on:
      - web

volumes:
  postgres_data:
  static_volume:
  media_volume:

接下来,更新Nginx配置以将媒体文件请求路由到“ media”文件夹:

    server web:5000;
}

server {

    listen 80;

    location / {
        proxy_pass http://hello_flask;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
        alias /home/app/web/project/static/;
    }

    location /media/ {
        alias /home/app/web/project/media/;
    }

}

重新构建镜像:

$ docker-compose down -v

$ docker-compose -f docker-compose.prod.yml up -d --build
$ docker-compose -f docker-compose.prod.yml exec web python manage.py create_db

最后一次测试:

  1. http://localhost:5000/upload 上传一个图像。
  2. http://localhost:5000/media/IMAGE_FILE_NAME 上查看图像。

    总结

    在本教程中,我们逐步介绍了如何使用 Postgres 将 Flask 应用程序容器化以进行开发。 我们还创建了可用于生产的 Docker Compose 文件,该文件将 Gunicorn 和 Nginx 添加到混合文件中以处理静态文件和媒体文件。 您现在可以在本地测试生产设置。
    就实际部署到生产环境而言,您可能需要使用:

  3. 完全托管的数据库服务(例如 RDSCloud SQL ),而不是在容器中管理自己的 Postgres 实例。

  4. 使用非 root 用户运行 dbnginx 服务。

您可以在 flask-on-docker 存储库中找到代码。

谢谢阅读!