1. 项目概述:为什么这套组合成了 Django 生产部署的“稳态标配”
最近帮某高校实验室部署一个课程作业管理系统,后端用 Django 写的,前端是 Vue,数据量不大但要求 7×24 小时在线、能扛住每学期期末选课那几天的瞬时并发。客户明确提了一条:“别用开发服务器 runserver,要像正规网站那样有域名、有 HTTPS、重启不丢服务、日志能查、出问题能快速回滚。”——这句话基本就锁死了技术栈方向:Docker 做环境隔离与可复现性,Uwsgi 做 Python 应用层的稳定承载,Nginx 做反向代理和静态资源分发,SSL 做传输加密。而宝塔面板,就是把这四层技术粘合起来的“可视化胶水”。
你可能听过“Django 不适合生产”这种老掉牙的说法,其实它完全适合,只是不能裸跑。Django 自带的 runserver 是纯调试工具,单线程、无超时控制、无进程管理、不支持 HTTPS,连基础的请求队列都做不了。真正让 Django 在生产中立住脚的,从来不是框架本身,而是它背后那一套被反复验证过的“应用网关+中间件+容器化”组合。这套组合里,Docker 解决了“在我机器上能跑,换台服务器就挂”的环境一致性问题;Uwsgi 解决了“Python 进程怎么不死、怎么扩缩容、怎么回收内存”的运行时治理问题;Nginx 解决了“用户怎么访问、静态文件放哪、怎么防攻击、怎么限流”的网络接入问题;SSL 则是现在所有正规网站的准入门槛——没有 HTTPS,浏览器地址栏会标“不安全”,微信小程序甚至直接拒绝加载你的接口。
宝塔面板在这里的角色,不是替代技术,而是降低操作门槛。它把原本需要手敲几十行命令、改五六处配置文件、查三四个日志路径的操作,压缩成几个勾选框和点击动作。比如申请 SSL 证书,手动操作要:登录 Let’s Encrypt、生成 CSR、配置 Nginx 的 ACME 验证路径、等待签发、合并证书链、重载 Nginx;在宝塔里,点“网站”→“SSL”→“申请”,选好域名,点“申请”,30 秒搞定。这不是偷懒,而是把重复性劳动从工程师脑子里腾出来,去解决更关键的问题:比如数据库连接池怎么调、Django 的缓存策略怎么设、Uwsgi 的 harakiri 超时值设多少才既防卡死又不误杀长请求。
所以这个标题里的每个词都不是随意堆砌的:Docker 是底座,Uwsgi 是心脏,Nginx 是皮肤,SSL 是通行证,宝塔是操作台。它们共同构成了一条从代码提交到用户访问的完整交付链路。接下来我会带你一节一节拆开看,不是照着文档念参数,而是告诉你每个开关拧多紧、每根线接到哪、哪个地方松了会漏风、哪个地方拧太紧会崩断。
2. 整体架构设计与方案选型逻辑
2.1 为什么非得是 Docker + Uwsgi + Nginx?能不能精简?
先说结论:可以精简,但不推荐,尤其对非全职运维的开发者。我见过三种常见“简化方案”,都踩过坑:
方案 A:Django + Gunicorn + Nginx(无 Docker)
Gunicorn 确实比 Uwsgi 更轻量,启动快,配置少。但它对长连接、异步任务、内存泄漏的处理不如 Uwsgi 成熟。我们曾在一个实时消息推送模块里用 Gunicorn,压测时发现 worker 进程在持续接收 WebSocket 心跳后,内存占用每小时涨 50MB,12 小时后 OOM。换成 Uwsgi 后,启用max-requests=1000和reload-on-rss=512参数,自动回收,问题消失。方案 B:Django + Uwsgi + 直连(无 Nginx)
Uwsgi 本身能监听 HTTP 端口,理论上可以不用 Nginx。但这就放弃了最核心的“分层防护”。Uwsgi 没有内置的 DDOS 防御、没有静态文件高效缓存、没有请求头过滤、没有负载均衡能力。当某个爬虫疯狂刷/admin/login/接口时,Uwsgi worker 全部卡在认证逻辑里,整个服务就不可用了。而 Nginx 层加个limit_req zone=login burst=5 nodelay;,瞬间把恶意请求挡在外面,Uwsgi 完全不受影响。方案 C:纯宝塔一键部署(无 Docker)
宝塔确实有“Python 项目”功能,能直接指定 Django 路径、Python 版本、启动命令。但它把所有东西都装在宿主机上:Python 包混在一起,不同项目的依赖版本冲突,升级 Django 时可能把另一个项目的包搞崩。去年帮一家教培公司迁移旧系统,他们就是用这种方式跑了三年,结果升级 Python 3.9 时,一个用django-crispy-forms==1.7.2的老项目直接报ImportError: cannot import name 'reverse'—— 因为新版本 Django 把reverse移到了另一个模块。Docker 的镜像分层机制,天然解决了这个问题:每个项目有自己的 Python 环境、自己的 pip 包、自己的配置文件,互不干扰。
所以最终选择 Docker + Uwsgi + Nginx,是权衡了稳定性、安全性、可维护性、可复现性后的最优解。Docker 提供环境沙盒,Uwsgi 提供 Python 运行时治理,Nginx 提供网络接入层治理,三者各司其职,边界清晰。就像一栋楼:Docker 是地基和承重墙,Uwsgi 是电梯和水电系统,Nginx 是门禁和前台——少哪一层,楼都能盖起来,但住着不舒服,也容易出事。
2.2 宝塔面板的定位:是“保姆”还是“监工”?
很多人对宝塔有误解,以为用了宝塔就等于放弃技术掌控。其实恰恰相反,宝塔真正的价值,是在你掌握底层原理之后,帮你把重复劳动自动化。它不是黑盒子,它的所有操作都有迹可循:
- 你点“网站”→“添加站点”,宝塔实际在
/www/server/panel/vhost/nginx/下生成一个.conf文件; - 你点“SSL”→“申请”,它实际在后台执行
acme.sh --issue -d example.com --nginx; - 你点“Docker”→“安装镜像”,它实际是
docker pull+docker run的封装。
这意味着:你可以随时绕过宝塔,直接编辑配置文件、手动执行命令、查看日志路径。宝塔只是把/var/log/uwsgi/、/www/wwwroot/myproject/、/www/server/panel/vhost/nginx/这些路径统一管理起来,给你一个图形界面入口。它不阻止你 ssh 进去改东西,反而帮你记住了这些路径在哪。
因此,在设计整个部署流程时,我的原则是:宝塔管“面”,手动管“点”。
- “面”:站点创建、SSL 申请、Docker 容器启停、Nginx 配置开关、日志查看——这些高频、重复、易出错的操作,全交给宝塔;
- “点”:Uwsgi 的具体参数调优、Django 的
settings.py中数据库连接池配置、Dockerfile 的多阶段构建优化、Nginx 的proxy_buffer大小设置——这些需要深度理解业务场景的细节,必须手动介入。
举个真实例子:某次部署一个图像上传接口,用户反馈大文件上传经常超时。宝塔里看 Nginx 日志全是504 Gateway Timeout。我立刻 ssh 进去,查/www/server/panel/vhost/nginx/myproject.conf,发现proxy_read_timeout 60;—— 这是宝塔默认值。但我们的业务要求支持 2GB 视频上传,光传输就要几分钟。我手动改成proxy_read_timeout 3600;,再加client_max_body_size 3000m;,问题解决。整个过程宝塔没参与,但它让我一眼就定位到该改哪个文件、哪个位置。
这就是理想状态:宝塔是你的操作台,不是你的大脑。
2.3 SSL 的必要性:不只是“浏览器不标红”,更是信任链起点
现在还有人觉得“小项目不用 HTTPS”,这是非常危险的认知。SSL 的作用远不止让地址栏变绿那么简单:
第一层:传输加密
所有 HTTP 明文传输的数据——包括 Django 的 CSRF token、Session ID、用户密码(即使前端做了哈希)、API 的 access_token——都会被中间人截获。我们曾用 Wireshark 在同一局域网内抓包,轻松拿到某内部管理系统的登录凭证。HTTPS 加密后,抓到的全是乱码。第二层:身份认证
Let’s Encrypt 签发的证书,背后是一整套 CA(证书颁发机构)信任链。当浏览器验证https://myapp.example.com的证书时,它不仅确认了“这个域名的流量是加密的”,更确认了“这个域名确实属于声称拥有它的人”。这杜绝了 DNS 劫持后伪造网站的可能性。第三层:现代 Web 功能准入
很多你以为“理所当然”的功能,其实强制要求 HTTPS:- 浏览器的
Geolocation API(获取用户位置); Service Worker(PWA 离线缓存);Web Push API(消息推送);- 微信小程序的
wx.request接口(必须 HTTPS 域名备案); - 甚至 Chrome 对 HTTP 网站的
localStorage会降级为unlimited模式,导致某些前端库异常。
- 浏览器的
所以 SSL 不是“锦上添花”,而是“入场券”。而宝塔集成的 Let’s Encrypt 申请,是目前最省心的方案:免费、自动续期、支持泛域名、与 Nginx 深度集成。唯一要注意的是,申请前必须确保域名已正确解析到服务器 IP,并且 80 端口未被其他服务占用——这是 Let’s Encrypt 的 HTTP-01 验证方式决定的,宝塔无法绕过这个物理限制。
3. 核心组件详解与实操配置要点
3.1 Docker 环境准备:从零开始构建可复现的 Python 运行时
Docker 的核心价值,在于把“Python 版本 + Django 版本 + 依赖包 + 启动命令 + 环境变量”打包成一个不可变的镜像。这样无论在开发机、测试机、生产机,只要docker run,出来的环境就一模一样。
3.1.1 基础镜像选择:alpine 还是 debian?python:3.11-slim 还是 python:3.11?
这是第一个关键决策点。我对比过三种主流基础镜像:
| 镜像名 | 大小 | 启动速度 | C 库兼容性 | 编译依赖安装难度 | 适用场景 |
|---|---|---|---|---|---|
python:3.11-slim | ~120MB | 快 | 高(glibc) | 低(apt-get) | 推荐:平衡大小与兼容性 |
python:3.11-alpine | ~55MB | 最快 | 低(musl libc) | 高(需 apk add build-base) | 小型工具类项目 |
python:3.11(完整版) | ~900MB | 慢 | 最高 | 最低 | 调试环境,不推荐生产 |
我们最终选用python:3.11-slim,理由很实在:
- Django 项目大量依赖 C 扩展(如
psycopg2、Pillow),alpine的 musl libc 与这些扩展预编译的二进制不兼容,每次pip install都要源码编译,耗时且容易失败; slim版本去掉了man、vim等开发工具,但保留了apt-get,安装gcc、postgresql-client等编译依赖依然方便;- 120MB 的体积,对现代服务器带宽来说毫无压力,换来的是极高的稳定性。
3.1.2 Dockerfile 编写:多阶段构建与最小化攻击面
一个生产级的 Django Dockerfile,绝不是简单几行FROM+COPY+CMD。我采用多阶段构建(Multi-stage Build),把构建环境和运行环境彻底分离:
# 构建阶段:安装编译依赖、编译 C 扩展、收集静态文件 FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN apt-get update && apt-get install -y \ gcc \ postgresql-client \ && rm -rf /var/lib/apt/lists/* RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN python manage.py collectstatic --noinput # 运行阶段:只复制必要文件,不带任何编译工具 FROM python:3.11-slim WORKDIR /app # 复制构建阶段生成的依赖和静态文件 COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /app/staticfiles /app/staticfiles COPY --from=builder /app/manage.py /app/manage.py COPY --from=builder /app/myproject /app/myproject # 创建非 root 用户,降低权限 RUN groupadd -g 1001 -f appuser && useradd -r -u 1001 -g appuser appuser USER appuser # 暴露 Uwsgi 默认端口 EXPOSE 8000 CMD ["uwsgi", "--ini", "uwsgi.ini"]这个写法的关键点在于:
- 第一阶段装
gcc,第二阶段完全不装:运行容器里没有编译器,黑客即使攻破应用,也无法在容器内编译恶意程序; - 静态文件在构建阶段就
collectstatic好:避免容器启动时再去执行,减少启动时间,也防止因权限问题导致收集失败; - 用非 root 用户运行:
USER appuser这一行,让 Uwsgi 进程以 UID 1001 运行,即使被利用,权限也受限; EXPOSE 8000只是声明,不开放端口:真正的端口映射由docker run -p 8000:8000控制,与 Nginx 的反向代理配合。
提示:
requirements.txt里一定要固定所有依赖版本,尤其是Django==4.2.11、psycopg2-binary==2.9.7这种核心包。用pip freeze > requirements.txt生成后,手动检查是否有--find-links或-e git+这种不可复现的条目,全部删掉。
3.1.3 容器网络与卷挂载:如何让 Docker 与宝塔协同工作
Docker 容器默认是网络隔离的,而宝塔的 Nginx 需要能访问到容器内的 Uwsgi 服务。这里有两个关键配置:
网络模式选择:不用
bridge(默认),改用host或自定义桥接网络。我倾向host,因为简单直接:docker run --network host ...,容器直接使用宿主机网络,Nginx 可以用127.0.0.1:8000访问 Uwsgi。但注意:host模式下容器端口不能冲突,所以 Uwsgi 的socket必须绑定到127.0.0.1:8000,而不是0.0.0.0:8000。卷挂载(Volume)设计:
/app/staticfiles→/www/wwwroot/myproject/static:让 Nginx 直接服务静态文件,不走 Uwsgi;/app/media→/www/wwwroot/myproject/media:用户上传的文件,同样由 Nginx 直接提供;/app/logs→/www/wwwroot/myproject/logs:Uwsgi 日志输出到这里,宝塔可以统一收集查看。
这样设计后,宝塔的“网站”功能就能无缝接管:你添加一个站点,根目录设为/www/wwwroot/myproject,Nginx 自动把/static/和/media/的请求转发到对应目录,其他请求才代理给127.0.0.1:8000。整个流程,Docker 只负责运行业务代码,Nginx 负责流量调度,职责分明。
3.2 Uwsgi 配置深度解析:不只是启动 Django,更是守护进程
Uwsgi 不是简单的“让 Django 跑起来”,它是 Django 的“守护神”和“调度员”。它的配置文件uwsgi.ini,决定了你的应用能扛多大压力、出问题时怎么恢复、内存怎么管理。
3.2.1 核心参数含义与取值逻辑
一个典型的uwsgi.ini如下(已根据 2C4G 服务器优化):
[uwsgi] # 基础设置 chdir = /app module = myproject.wsgi:application master = true processes = 4 threads = 2 enable-threads = true harakiri = 30 max-requests = 1000 reload-on-rss = 512 vacuum = true die-on-term = true # socket 设置 socket = 127.0.0.1:8000 chmod-socket = 664 chown-socket = www:www uid = 1001 gid = 1001 # 日志与监控 logto = /app/logs/uwsgi.log log-maxsize = 10000000 log-backupname = /app/logs/uwsgi.log.old stats = 127.0.0.1:9191 memory-report = true逐项解释其背后的工程考量:
processes = 4:不是 CPU 核数的简单倍数。Django 是 I/O 密集型(数据库、缓存、HTTP 请求),不是 CPU 密集型。2C 服务器上,4 个进程足够应对 500 QPS,再多反而因进程切换增加开销。实测:从 2 进程升到 4 进程,QPS 从 320 升到 480;再升到 6 进程,QPS 反降到 450。threads = 2:每个进程内开 2 个线程,处理并发请求。Uwsgi 的线程模型比进程模型轻量,适合处理短连接。但线程太多会导致 GIL(全局解释器锁)争抢,反而降低吞吐。enable-threads = true是启用线程的前提。harakiri = 30:这是“自杀超时”。如果一个请求处理超过 30 秒,Uwsgi 强制杀死该 worker。这是防雪崩的关键:避免一个慢查询拖垮所有 worker。但注意,这个值必须大于你最长的业务逻辑耗时。比如导出报表要 45 秒,这里就得设成50,否则报表永远生成失败。max-requests = 1000:每个 worker 处理 1000 个请求后自动重启。这是防内存泄漏的兜底策略。Python 的引用计数机制有时无法及时回收循环引用,长期运行后内存缓慢上涨。定期重启,一劳永逸。reload-on-rss = 512:worker 进程内存 RSS(常驻集)超过 512MB 自动重启。这是max-requests的补充,针对突发性内存暴涨(如一次加载 10 万条数据到内存)。vacuum = true:worker 退出时,自动清理 Unix socket 文件和 pid 文件。避免下次启动时报Address already in use。die-on-term = true:收到SIGTERM信号(Docker stop 时发送)时,优雅退出。这是容器化部署的必备选项,保证docker stop不是暴力kill -9。
注意:
chown-socket = www:www这行很重要。宝塔的 Nginx 进程默认以www用户运行,Uwsgi 的 socket 文件必须让www用户有读写权限,否则 Nginx 无法连接。chmod-socket = 664设为 664,www组可读写,www用户可读写,完美匹配。
3.2.2 启动与调试:如何确认 Uwsgi 真正跑起来了?
不要只看docker ps里容器是Up状态。要验证 Uwsgi 是否健康,分三步:
- 检查进程:
docker exec -it myproject-container ps aux | grep uwsgi,应看到master进程和 4 个worker进程; - 检查端口:
docker exec -it myproject-container netstat -tuln | grep :8000,应显示127.0.0.1:8000处于LISTEN; - 检查日志:
docker logs myproject-container | tail -20,末尾应有spawned uWSGI master process和spawned uWSGI worker 1等字样。
如果卡在第 2 步,大概率是socket绑定错了。常见错误:socket = /tmp/uwsgi.sock(Unix socket)但 Nginx 配置里写的是proxy_pass http://127.0.0.1:8000(TCP socket)。两者必须一致。我们全程用 TCP socket,因为更简单,也便于宝塔的 Nginx 配置。
3.3 Nginx 配置实战:不只是反向代理,更是第一道防火墙
Nginx 在这里是真正的“门神”。它不处理业务逻辑,但决定了谁可以进来、进来后怎么分发、异常时怎么响应。
3.3.1 宝塔生成的默认配置分析与改造
宝塔添加站点后,会在/www/server/panel/vhost/nginx/myproject.conf生成配置。默认内容大致如下:
server { listen 80; server_name myproject.example.com; index index.html index.htm default.htm; root /www/wwwroot/myproject; # SSL 配置(申请后自动添加) # include /www/server/panel/vhost/ssl/myproject.example.com/ssl.conf; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态文件直接服务 location /static/ { alias /www/wwwroot/myproject/static/; } location /media/ { alias /www/wwwroot/myproject/media/; } }这个配置有三个致命缺陷,必须修改:
缺陷 1:缺少超时设置
默认proxy_read_timeout是 60 秒,但 Django 的harakiri是 30 秒。如果 Uwsgi 在 30 秒内杀了请求,Nginx 还在等,就会返回504。解决方案:proxy_read_timeout 35;(比 harakiri 大 5 秒,留出网络传输余量)。缺陷 2:缺少缓冲区设置
大文件上传或响应时,Nginx 默认缓冲区太小,容易502 Bad Gateway。加两行:proxy_buffering on; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k;缺陷 3:缺少安全头
加上这些头,提升基础安全水位:add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline' 'unsafe-eval';" always;
3.3.2 SSL 配置:Let’s Encrypt 自动续期的可靠性保障
宝塔申请 SSL 后,会自动在配置里加入:
include /www/server/panel/vhost/ssl/myproject.example.com/ssl.conf;这个ssl.conf文件里包含了证书路径、协议版本、加密套件。但有两个隐藏风险点:
风险点 1:TLS 版本过旧
默认可能包含TLSv1.1,而现代浏览器已弃用。手动编辑ssl.conf,把ssl_protocols改为:ssl_protocols TLSv1.2 TLSv1.3;风险点 2:自动续期失败静默
Let’s Encrypt 证书 90 天过期,宝塔默认每天凌晨 2 点检查续期。但如果那天服务器关机、磁盘满了、或 DNS 解析临时故障,续期就会失败,且宝塔不会发告警。解决方案:加一个 cron 任务,每周检查证书剩余天数并邮件通知:# 编辑 crontab 0 3 * * 0 /usr/bin/curl -s https://myproject.example.com | grep "200 OK" > /dev/null || echo "HTTPS down!" | mail -s "ALERT: myproject SSL" admin@example.com更稳妥的是在宝塔“计划任务”里添加一个“Shell 脚本”,内容为:
#!/bin/bash DAYS=$(openssl x509 -in /www/server/panel/vhost/ssl/myproject.example.com/fullchain.pem -noout -days | awk '{print $2}') if [ $DAYS -lt 15 ]; then echo "SSL certificate for myproject.example.com expires in $DAYS days!" | mail -s "URGENT: SSL Expiry" admin@example.com fi
4. 完整部署流程与关键环节实现
4.1 环境初始化:从一台裸 CentOS 服务器到可部署状态
假设你有一台全新的阿里云 ECS,系统为 CentOS 7.9,IP 为192.168.1.100,域名myproject.example.com已解析到此 IP。以下是完整初始化步骤,每一步我都注明“为什么这么做”:
4.1.1 系统基础加固(5 分钟)
# 1. 更新系统(修复已知漏洞) yum update -y # 2. 安装基础工具(后续排查必备) yum install -y vim wget curl git net-tools # 3. 关闭 SELinux(宝塔官方要求,避免权限冲突) sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config setenforce 0 # 4. 开放防火墙端口(宝塔 Web 界面、SSH、HTTP、HTTPS) firewall-cmd --permanent --add-port=8888/tcp # 宝塔面板端口 firewall-cmd --permanent --add-port=22/tcp # SSH firewall-cmd --permanent --add-port=80/tcp # HTTP firewall-cmd --permanent --add-port=443/tcp # HTTPS firewall-cmd --reload注意:
setenforce 0是临时关闭,/etc/selinux/config修改是永久关闭。宝塔与 SELinux 兼容性差,强行开启会导致 Nginx 无法读取/www/wwwroot/下的文件,报Permission denied。这不是偷懒,是经过大量线上验证的取舍。
4.1.2 宝塔面板安装与 Docker 插件启用(3 分钟)
# 一键安装(官方最新稳定版) yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh # 安装完成后,用浏览器访问 http://192.168.1.100:8888,按提示设置管理员账号 # 登录后,进入“软件商店” → 搜索“Docker” → 点击“安装” # 安装完成后,再安装“宝塔Docker管理器”插件(增强版 UI)安装完 Docker 插件后,你会在左侧菜单看到“Docker”图标。此时 Docker 已就绪,但还不能直接docker run,因为宝塔默认把 Docker 的 socket 文件权限设为root:root,而宝塔 Web 界面是以www用户运行的,无法访问。需要手动修复:
# 将 docker 组添加 www 用户 usermod -aG docker www # 重启宝塔,使组生效 bt restart4.1.3 域名与网站创建(2 分钟)
- 进入宝塔“网站” → “添加站点”,填写:
- 域名:
myproject.example.com - 根目录:
/www/wwwroot/myproject - PHP 版本:
纯静态(因为我们用 Nginx 代理,不走 PHP)
- 域名:
- 点击“提交”,宝塔自动创建目录、生成 Nginx 配置、设置权限。
此时,访问http://myproject.example.com会看到宝塔默认的欢迎页。下一步,我们要把 Django 项目放进去。
4.2 Django 项目结构标准化与部署包准备
Django 项目不能直接扔进/www/wwwroot/myproject就完事。必须遵循一个标准结构,让 Docker 和 Uwsgi 能正确识别。
4.2.1 推荐的项目目录树
/www/wwwroot/myproject/ ├── Dockerfile # 构建镜像用 ├── docker-compose.yml # (可选)多容器编排 ├── requirements.txt # 依赖清单 ├── uwsgi.ini # Uwsgi 配置 ├── manage.py # Django 管理脚本 ├── myproject/ # Django 项目包(含 settings.py, wsgi.py) │ ├── __init__.py │ ├── settings/ │ │ ├── __init__.py │ │ ├── base.py # 公共配置 │ │ ├── production.py # 生产环境配置(关键!) │ │ └── local.py # 本地开发配置 │ ├── urls.py │ ├── wsgi.py # Uwsgi 入口 │ └── asgi.py ├── static/ # 收集后的静态文件(由 collectstatic 生成) ├── media/ # 用户上传文件(由 Nginx 直接服务) └── logs/ # Uwsgi 日志(由 Docker volume 挂载)4.2.2 生产环境 settings.py 关键配置
myproject/settings/production.py是核心,必须包含以下内容:
from .base import * # 安全相关(必须!) DEBUG = False ALLOWED_HOSTS = ['myproject.example.com', '192.168.1.100'] # 严格限定域名 SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') # 从环境变量读取,不硬编码 SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_HSTS_SECONDS = 31536000 # HSTS 1年 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True X_FRAME_OPTIONS = 'DENY' # 数据库(推荐使用环境变量) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ.get('DB_NAME', 'myproject'), 'USER': os.environ.get('DB_USER', 'postgres'), 'PASSWORD': os.environ.get('DB_PASSWORD', ''), 'HOST': os.environ.get('DB_HOST', '127.0.0.1'), 'PORT': os.environ.get('DB_PORT', '5432'), } } # 静态文件与媒体文件 STATIC_ROOT = '/app/staticfiles' # Docker 内路径 STATIC_URL = '/static/' MEDIA_ROOT = '/app/media' MEDIA_URL = '/media/' # 日志配置(输出到 stdout,供 Docker logs 查看) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', }, }, 'root': { 'handlers': ['console'], 'level': 'INFO', }, }实操心得:
SECRET_KEY绝对不能写死在代码里!必须通过docker run -e DJANGO_SECRET_KEY=xxx传入。我曾在一个项目里把 key 硬编码,结果被扫描器扫出,整个数据库差点被拖库。环境变量是最低成本的安全加固。