本文是一步步的教学指南,详细说明如何在Docker中配置Django与Postgres一起运行。对于生产环境,我们将加入Nginx和Gunicorn。我们还将讨论如何使用Nginx来托管Django的静态和媒体文件。
依赖
Django on Docker 系列:
- 使用 Docker 包装 Django、PostgreSQL、Gunicorn 和 Nginx(这篇教程!)
- 保护容器化 Django 应用的安全 使用 Let’s Encrypt
- 在 AWS 上部署 Django 使用 Docker 和 Let’s Encrypt
创建一个新的项目目录,并且在其中建立一个新的Django项目。
您可以将 virtualenv 和 Pip 替换成 Poetry 或 Pipenv 。更多,请参阅 现代 Python 开发环境 .
在浏览器中打开 http://localhost:8000/ 查看 Django 欢迎界面。完成后关闭服务器。之后退出并删除虚拟环境。我们现在有了一个可以使用的简单的 Django 项目。
在'app' 目录下创建一个_requirements.txt_文件,并将Django作为依赖项添加进去。
既然我们要迁移到Postgres,可以去‘app’目录下删除_db.sqlite3_文件了。
你的项目文件夹应该如下所示:
Docker(一个开源的应用容器引擎)如果你还没有安装Docker的话,可以在“app”目录中添加一个Dockerfile:
所以我们从一个基于 slim-buster
的 Python 3.11.4 的 Docker 镜像 开始。然后我们定义了 工作目录 以及两个环境变量:
PYTHONDONTWRITEBYTECODE
:禁止 Python 生成 pyc 文件(等同于使用python -B
参数)PYTHONUNBUFFERED
:防止 Python 缓存标准输出和标准错误(等同于使用python -u
参数)
最后,我们升级了Pip,复制了_requirements.txt_文件(注意保留英文文件名),安装了依赖,并复制了Django项目。
Review Docker 最佳实践指南:Python 开发人员版 _以了解更多关于如何结构化 Dockerfile 的信息,另外还有了解一些针对 Python 项目配置 Docker 的最佳实践。
</TRANSLATION>
接下来,请在项目根目录添加一个 docker-compose.yml 文件即可。
查看 Compose 文件参考 ,了解这个文件是如何工作的.
更新 settings.py 文件中的 SECRET_KEY
、DEBUG
和 ALLOWED_HOSTS
变量。
确保在文件顶部加入导入语句:
# 在这里添加具体的导入语句,例如:import 模块名
然后,在项目根目录下,创建一个 .env.dev 文件来存储开发环境变量值,例如:
创建镜像。
镜像构建完成后,运行该容器,
访问 http://localhost:8000/ 再次查看欢迎页面。
Postgres(PostgreSQL数据库)如果还是不行,可以检查日志中的错误,通过
_docker-compose logs -f_
。
要配置 PostgreSQL,我们需要在 docker-compose.yml 文件中添加一个新的服务,更新 Django 的设置,并安装 Psycopg2。
首先,在 docker-compose.yml 文件中添加一个名为 db
的新服务:
为了在容器的生命周期结束后仍然保留数据,我们配置了一个卷。我们将 postgres_data
绑定到容器中的 /var/lib/postgresql/data/
路径。
我们也添加了一个环境变量来定义默认数据库的名称为,并设置了用户名和密码以供使用。
查看Postgres Docker Hub页面上的环境变量部分以获取更多信息。
我们还需要给web
服务添加一些新的环境变量,请参考以下内容更新_.env.dev_文件如下所示:
新的环境变量
在 settings.py 文件里更新 DATABASES
部分:
这里,数据库是根据我们刚刚定义的环境变量进行配置的。请留意默认设置。
在 requirements.txt 中添加 Psycopg2:
构建新的镜像,并启动两个容器。
运行迁移命令:
$ docker-compose exec web python manage.py migrate --noinput
这条命令用于执行Docker-compose中定义的web服务的数据库迁移。docker-compose
是一个用于定义和运行多容器Docker应用的工具,web
表示要执行命令的服务,python manage.py migrate
是运行Django应用的数据库迁移命令,--noinput
参数表示在迁移过程中不提示输入。
遇到这样的错误?
_django.db.utils.OperationalError: FATAL: 数据库“hello_djangodev”不存在
运行
docker-compose down -v
以移除卷和容器。然后,重新构建镜像,运行容器,并执行迁移。
检查默认的Django表是否已经创建好了。
你可以通过运行以下操作检查磁盘卷是否已创建:
你应该看到类似的东西:
接下来,在“app”目录中添加一个_entrypoint.sh_文件,以确保在执行迁移和运行Django开发服务器前Postgres运行正常。
修改本地文件权限:
然后,更新 Dockerfile,复制 entrypoint.sh 文件,并将其设置为 Docker entrypoint 命令。
在 .env.dev 文件中添加 DATABASE
环境变量:
再试一下,
- 重新构建图像
- 启动容器实例
- 在浏览器中打开尝试一下 http://localhost:8000/
尽管添加了Postgres,我们仍然可以为Django单独构建一个Docker容器镜像,只要DATABASE
环境变量没有设置为postgres
。为了测试,我们可以构建一个新的镜像之后,然后运行一个新的容器。
$ docker build -f ./app/Dockerfile -t hello_django:latest ./app
$ docker run -d -p 8006:8000 -e "SECRET_KEY=请修改我" -e "DEBUG=1" -e "DJANGO_ALLOWED_HOSTS=*" hello_django python /usr/src/app/manage.py runserver 0.0.0.0:8000
# 请根据需要修改环境变量SECRET_KEY的值。
在http://localhost:8006,你应该能看到欢迎页面。
另外,你可以将_entrypoint.sh_脚本中的数据库清空和迁移操作注释掉,这样它们就不会在每次启动或重启容器时执行。
相反,你可以在容器启动之后手动运行它们,就像这样。
Gunicorn (龟牛corn)继续往下,对于生产环境中,让我们在requirements文件中加入Gunicorn,一个生产级的WSGI服务器。
对WSGI和Gunicorn感到好奇吗?查阅课程中的_ WSGI 章节, 构建你自己的Python Web框架课程 _。
由于我们仍然想在开发中使用 Django 的内置服务器,创建一个名为 docker-compose.prod.yml 的新 compose 文件用于生产环境。
如果你有多个环境设置,可能想使用一个docker-compose.override.yml 配置文件。删除你将基础配置添加到一个 docker-compose.yml 文件中,然后使用 docker-compose.override.yml 文件来根据不同的环境调整这些设置。采取这种方法,可以更好地反映覆盖配置的概念。
请注意默认的 command
。我们运行的是 Gunicorn 而不是 Django 开发服务器。因为生产环境中不需要它,所以我们从 web
服务中移除了卷。最后,我们使用了环境变量文件来定义两个服务各自的环境变量,这些环境变量将在运行时传递给容器。
将这两个文件添加到项目根目录下。你可能希望将它们排除在版本控制之外,那就把它们添加到_.gitignore_文件中。
卸载开发容器(并使用-v
标志卸载相关卷)(关于如何卸载,请参考这里):
然后,构建生产环境的镜像并开始运行容器实例:
验证 hello_django_prod
数据库和默认的 Django 表是否已创建。访问管理页面 http://localhost:8000/admin。不再加载静态文件,这是因为 Debug 模式已关闭,所以这是正常的。我们很快会解决这个问题。
用于生产的 Dockerfile:如果容器再次无法启动,请通过
docker-compose -f docker-compose.prod.yml logs -f
查看错误日志。
每次我们运行容器时,你有没有注意到我们仍然在运行数据库的刷新命令(这会清空数据库)和迁移命令?这在开发环境中是可以接受的,但在生产环境中,让我们创建一个新的入口点文件。
生产环境入口脚本 entrypoint.prod.sh :
修改本地文件权限如下:
要使用该文件,请为生产环境创建一个新的名为 Dockerfile.prod 的 Dockerfile。
在这里,我们使用了 Docker 的多阶段构建来减少最终镜像的体积。简单来说,builder
是一个临时镜像,用于构建 Python wheels。然后将 wheels 复制进最终的生产镜像中,并舍弃 builder
镜像。
你可以更进一步地利用 多阶段构建 ,并使用一个 Dockerfile,而不是创建两个 Dockerfile。想一想这种方法和使用两个不同文件相比,有哪些好处和缺点。
你发现我们创建了一个非root用户账户了吗?通常情况下,Docker容器内的进程默认以root身份运行。这样做不太好,因为如果攻击者设法从容器逃脱,他们就可以控制Docker主机。假设你在容器里获得了root权限,你同样可以在主机上获得root权限。
在 docker-compose.prod.yml (生产配置文件) 文件中的 web
服务,将其更新为使用 Dockerfile.prod (生产构建文件) 进行构建。
试试看,
启动并构建生产环境的Docker容器,并运行数据库迁移。
$ 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 migrate --noinput
Nginx配置
接下来,让我们加入Nginx,让它作为Gunicorn的反向代理服务器来处理客户端请求并提供静态文件。
在 docker-compose.prod.yml 中加上服务如下:
接下来,在本地项目根目录下创建以下文件夹和文件:
Dockerfile:
FROM nginx:1.25 RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d
查阅_使用NGINX和NGINX Plus作为与uWSGI和Django配合使用的应用网关配置 获取更多有关如何配置Nginx与Django配合使用的资料。
然后,在 docker-compose.prod.yml 文件中,将 web
服务配置的 ports
替换为 expose
。
现在,8000端口仅对内部的其他Docker服务开放,不会再映射到主机了。
关于端口和暴露的区别,可以参考一下这个问题的讨论在Stack Overflow上的内容.
再试试看。
上述命令用于在生产环境中停止并重建Docker容器,然后执行数据库迁移。
$ 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 migrate --noinput
确保应用程序在http://localhost:1337上启动并正常运行。
你的项目结构现在应该如下所示:(请查看项目的根目录)。
做好了就把容器拿下去:
由于 Gunicorn 是一个应用服务器(应用程序服务器),它不会提供静态文件。在这种情况下,我们应该如何处理静态文件和媒体文件呢?
静态文件更新一下 settings.py 文件
开发任何对 **http://localhost:8000/static/*** 的请求现在都将从 staticfiles 目录中处理。
测试时,首先像平常一样重新构建镜像并启动新的容器实例。确保静态文件仍然可以通过http://localhost:8000/admin 正确提供进行验证。
制作在生产环境中,在 _docker-compose.prod.yml
中为 web
和 nginx
服务添加一个卷,以便每个容器都可以共享一个名为 "staticfiles" 的目录。
我们还需要在_Dockerfile.prod_中创建“/home/app/web/staticfiles”目录。
这为什么有必要?
Docker Compose 通常会将命名卷挂载为 root 用户权限。因为我们在使用非 root 用户,如果该目录不存在,运行 collectstatic
命令时会遇到权限被拒绝的错误。
要解决这个问题,你可以:
或者结合使用多种方法。
我们用了前者。
接下来,更新Nginx配置,将静态文件请求指向“staticfiles”目录:
关闭开发容器:
测试:
再次,对http://localhost:1337/static/*
的请求将被处理来自"staticfiles"目录。
(Note: After revisiting the suggestions, the most fluent and contextually appropriate version seems to be:)
再次,对http://localhost:1337/static/*
的请求将由"staticfiles"目录处理。
访问 http://localhost:1337/admin,并确保它们加载正确。
你也可以通过运行 docker-compose -f docker-compose.prod.yml logs -f
查看日志,确认静态文件请求是否已成功通过 Nginx 提供。
做完后把容器带来:
为了测试媒体文件的处理功能,首先创建一个新的Django应用程序。
运行 `docker-compose up -d --build` 命令来启动并构建容器,然后使用 `docker-compose exec web python manage.py startapp upload` 命令在web服务中执行Python管理命令以启动应用程序。
在 settings.py 文件里,把新应用程序加到 INSTALLED_APPS
里
文件路径:app/upload/views.py
在“app/upload”目录下新建一个名为“templates”的目录, 并在该目录中创建一个新的模板文件,命名为“upload.html”。
_app/hellodjango/urls.py :
_app/hellodjango/settings.py
开发部分测试:
你应该能够在此 http://localhost:8000/ 上传图片,然后在该地址 http://localhost:8000/media/IMAGE_FILE_NAME 查看图片。
制作在生产环境中,为web
和nginx
组件添加一个额外的卷。
在名为 Dockerfile.prod 的文件中,添加以下命令来创建文件夹(folder):/home/app/web/mediafiles
再更新一下 Nginx 配置:
在 settings.py 文件中添加以下内容:
重建:
再试一次最后:
- 在http://localhost:1337/上传一张图片到该地址。
- 之后,在http://localhost:1337/media/IMAGE_FILE_NAME查看上传的图片。
_如果你看到 413 Request Entity Too Large 错误,你需要增加客户端请求正文的最大允许大小,可以在Nginx配置的服务器或位置上下文中进行设置。_
例如:
结尾location / { proxy_pass http://hello_django; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; client_max_body_size 100M; }
在这篇教程中,我们介绍了怎样为开发环境将一个使用 Postgres 的 Django 网络应用容器化。我们还创建了一个生产就绪的 Docker Compose 文件,该文件配置了 Gunicorn 和 Nginx 以处理静态和媒体文件。你现在可以在本地测试一下生产环境下的设置。
就实际部署到生产环境而言,你可能希望使用的是:比如
其他生产技巧,可以查看此讨论。
你可以在这个django-on-docker代码库找到代码。
还有一个较旧的Pipenv版本的代码在这里可以找到它 .
谢谢你看完了这篇
Django与Docker系列: