Day 19:Docker + Nginx 部署上线,让你的商城真正跑起来!

摘要:项目写完了,联调也通过了,但代码还在服务器上裸跑?今天这篇,手把手带你把商城从开发环境打包,部署到生产服务器上。Docker 容器化、Nginx 反向代理、Gunicorn 高性能应用服务器、HTTPS 证书配置、域名备案流程,一套完整上线流程全走完。三个核心配置文件(Dockerfile、docker-compose.yml、nginx.conf)全部完整可用,带详细注释。最后再教你用 AI 写部署脚本和排错指南,省下大量踩坑时间。


开发完了,用户还看不到?

前 18 天我们把商城从 0 搭到了联调完毕。

Django 的 API 写好了,DRF 的接口通了,MySQL 8.0 数据跑起来了,Redis 缓存也配上了,uni-app 前端也对接完了。

但是——你的项目现在是什么状态?

大概率是这样:在服务器上跑着python manage.py runserver 0.0.0.0:8000,裸奔着。

这就好比你开了一家店,招牌没挂、门没装、灯没亮,客人从门口路过都不知道这是不是正经营业。

runserver是开发用的调试服务器,单线程、不安全、不支持并发。性能?不存在的。安全?更不存在。

那怎么让你的商城真正跑起来,让用户通过域名 + HTTPS 安全地访问到?

今天的主角来了:Docker + Nginx + Gunicorn三件套。

这三样东西在生产环境部署 Django 项目里是标配,学会了这个,以后部署任何 Python Web 项目都是同一套流程。


三个家伙各管什么

动手之前,先搞清楚每个角色负责什么,这样后面看配置才不会一头雾水。

Gunicorn —— 干活的工人

Django 自带的runserver是单线程的,来了 100 个请求就排队等着,慢得要死。

Gunicorn 是一个 WSGI HTTP Server,它能起多个 worker 进程,真正处理并发请求。你可以把它理解成:一个车间里的多个工人,同时处理多个订单。

Nginx —— 门面担当

Nginx 是整个架构的入口,它干三件事:

  • 反向代理:把用户的请求根据规则转发给后面的 Gunicorn
  • 静态文件服务:CSS、JS、图片这些静态资源直接由 Nginx 返回,不需要经过 Django,性能提升巨大
  • HTTPS 终端:SSL 证书的加解密都在 Nginx 层完成,后面用 HTTP 通信就行

Nginx 处理静态文件的能力非常强,单机轻松应对上万并发。让 Django 去处理图片请求?那简直是大材小用。

Docker —— 一键搬家

Docker 把你的整个运行环境打包成一个镜像:Python 版本、系统依赖、代码、配置文件,全在一起。

换服务器?拉镜像,docker compose up -d,30 秒搞定。不用再手动装 Python、装依赖、配环境变量了。

而且环境一致性有保障——开发、测试、生产用的都是同一个镜像,不会出现"在我电脑上能跑"的经典问题。

一句话总结:Nginx 负责接客和分流 → Gunicorn 负责实际干活 → Docker 负责打包整个环境。


动手实战:从零到上线

第一步:编写 Dockerfile

Dockerfile 就是"打包说明书",告诉 Docker 怎么一步步构建你的运行镜像。用多阶段构建来减小最终镜像体积:

# ========== 第一阶段:构建依赖 ========== FROM python:3.11-slim AS builder WORKDIR /app # 安装系统依赖(编译 MySQL 客户端等需要) RUN apt-get update && apt-get install -y \ gcc \ default-libmysqlclient-dev \ pkg-config \ && rm -rf /var/lib/apt/lists/* # 先复制依赖文件,利用 Docker 缓存层 COPY requirements.txt . RUN pip install --no-cache-dir --prefix=/install -r requirements.txt # ========== 第二阶段:运行镜像 ========== FROM python:3.11-slim WORKDIR /app RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* # 从构建阶段拷贝安装好的 Python 包(运行时不需要 gcc 这些编译工具) COPY --from=builder /install /usr/local COPY . . # 收集静态文件(生产模式需要) RUN python manage.py collectstatic --noinput EXPOSE 8000 # 启动命令:4 个 worker,绑定 0.0.0.0:8000,日志输出到 stdout(docker logs 可见) CMD ["gunicorn", "shop.wsgi:application", \ "--bind", "0.0.0.0:8000", \ "--workers", "4", \ "--threads", "2", \ "--timeout", "120", \ "--access-logfile", "-", \ "--error-logfile", "-"]

几个关键点:

  • 多阶段构建:第一阶段装编译工具(gcc 这些),第二阶段只拷贝装好的包。最终镜像里没有编译工具,体积能从 1GB+ 降到 300MB 左右
  • 依赖缓存:先 COPY requirements.txt,再 COPY 代码。改了业务代码不会触发重新装依赖,构建速度快
  • workers 数量:经验公式是2 * CPU核心数 + 1,4 核机器就配 4-5 个 worker。太多反而会因上下文切换变慢

第二步:编写 docker-compose.yml

光有 Dockerfile 还不够。项目不只有 Django,还有 MySQL、Redis 这些服务。

docker-compose.yml是编排清单,定义所有服务和它们之间的关系,一键启动:

version:"3.8"services:# ========== MySQL 数据库 ==========mysql:image:mysql:8.0restart:alwaysenvironment:MYSQL_ROOT_PASSWORD:${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE:shop_mallTZ:Asia/Shanghaiports:-"127.0.0.1:3306:3306"# 只监听本地,不暴露到公网volumes:-mysql_data:/var/lib/mysql# 数据持久化command:---character-set-server=utf8mb4---default-authentication-plugin=mysql_native_passwordhealthcheck:# 健康检查:MySQL ready 前应用不启动test:["CMD","mysqladmin","ping","-h","localhost"]interval:10stimeout:5sretries:5# ========== Redis 缓存 ==========redis:image:redis:7-alpinerestart:alwaysports:-"127.0.0.1:6379:6379"volumes:-redis_data:/datacommand:redis-server--appendonly yes--requirepass ${REDIS_PASSWORD}healthcheck:test:["CMD","redis-cli","-a","${REDIS_PASSWORD}","ping"]interval:10stimeout:5sretries:5# ========== Django 应用 ==========web:build:context:.dockerfile:Dockerfilerestart:alwaysports:-"127.0.0.1:8000:8000"# 只监听本地,由 Nginx 转发volumes:-static_files:/app/static# 静态文件共享给 Nginx-media_files:/app/media# 用户上传文件env_file:-.env# 环境变量文件(敏感配置不硬编码)environment:-DJANGO_SETTINGS_MODULE=shop.settings.prod-DATABASE_HOST=mysql# 指向 compose 内的 mysql 服务-REDIS_HOST=redisdepends_on:mysql:condition:service_healthy# 数据库就绪后才启动应用redis:condition:service_healthy# ========== Nginx 反向代理 ==========nginx:image:nginx:1.25-alpinerestart:alwaysports:-"80:80"-"443:443"volumes:-./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro-./nginx/ssl:/etc/nginx/ssl:ro# SSL 证书目录-static_files:/usr/share/nginx/html/static:ro-media_files:/usr/share/nginx/html/media:rodepends_on:-webvolumes:mysql_data:# 命名卷:容器删了重建,数据不丢redis_data:static_files:media_files:

设计讲究:

  • 网络隔离:MySQL 和 Redis 都绑定127.0.0.1,外网扫描根本扫不到这两个端口。只有容器之间能通信,安全性拉满
  • 健康检查:MySQL/Redis 配了 healthcheck,web 的depends_on配condition: service_healthy,确保数据库完全就绪后应用才启动——不再踩"应用启动但数据库还没 ready"的坑
  • 环境变量外置:密码放.env文件,不硬编码在 yml 里
  • 数据持久化:命名卷保证容器重建数据不丢

第三步:配置 Nginx

Nginx 配置放在nginx/nginx.conf(核心块):

# 上游 Gunicorn 服务(扩容时加 server web2:8000 即可,Nginx 自动轮询) upstream shop_backend { server web:8000; } # ========== HTTP → HTTPS 强制跳转 ========== server { listen 80; server_name your-domain.com; # Let's Encrypt 验证路径 location /.well-known/acme-challenge/ { root /var/www/certbot; } # 其余所有请求跳转到 HTTPS location / { return 301 https://$host$request_uri; } } # ========== HTTPS 主配置 ========== server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # 安全头 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; client_max_body_size 20M; # 允许上传最大 20M(图片、头像等) # Gzip 压缩 gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1000; # ---------- 静态文件:Nginx 直接返回(不经过 Django)---------- location /static/ { alias /usr/share/nginx/html/static/; expires 30d; # 缓存 30 天 add_header Cache-Control "public, immutable"; access_log off; } # ---------- 用户上传的媒体文件 ---------- location /media/ { alias /usr/share/nginx/html/media/; expires 7d; } # ---------- API 请求:反向代理到 Gunicorn ---------- location /api/ { proxy_pass http://shop_backend; 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; proxy_connect_timeout 60s; proxy_read_timeout 120s; } # ---------- 健康检查端点 ---------- location /health/ { proxy_pass http://shop_backend; access_log off; } }

核心设计:

  • upstream 块:定义后端服务地址,扩展时加server web2:8000就行,Nginx 自动轮询
  • HTTP 跳 HTTPS:所有 80 端口请求 301 重定向到 443,确保用户始终走加密通道
  • 静态文件分离:/static/、/media/由 Nginx 直接返回,不经过 Django。原来 Django 处理一个静态文件要 50ms,Nginx 只要 2ms
  • 安全头:HSTS 强制浏览器只用 HTTPS、X-Frame-Options 防点击劫持、X-Content-Type-Options 防 MIME 嗅探

第四步:准备环境变量文件

项目根目录创建.env,管理所有敏感配置:

# .env - 生产环境变量(千万别提交到 Git!)DJANGO_SETTINGS_MODULE=shop.settings.prodSECRET_KEY=你的django-secret-keyDEBUG=FalseALLOWED_HOSTS=your-domain.com,www.your-domain.comMYSQL_ROOT_PASSWORD=xxxMYSQL_PASSWORD=xxxREDIS_PASSWORD=xxx

记得在.gitignore里加上.env!密码泄露到 GitHub 上可不是闹着玩的。

第五步:申请 SSL 证书

推荐用Let’s Encrypt的免费证书,certbot申请,90 天自动续期:

# 1. 安装 certbotaptupdate&&aptinstall-ycertbot# 2. 临时停 Nginx(释放 80 端口给 certbot 验证)dockercompose stop nginx# 3. 申请证书(standalone 模式)certbot certonly--standalone-dyour-domain.com-dwww.your-domain.com# 4. 把证书拷到项目目录cp/etc/letsencrypt/live/your-domain.com/{fullchain.pem,privkey.pem}./nginx/ssl/# 5. 启动所有服务dockercompose up-d# 6. 验证自动续期certbot renew --dry-run

证书 90 天到期,certbot 会自动加定时任务续期。只要 80 端口不长期关闭,续期全自动。

第六步:域名 + SSL + 备案

域名这块新手容易懵,完整流程捋一遍:

  1. 买域名:阿里云、腾讯云、Cloudflare 都行。.com大概 50-70 元/年,.cn更便宜
  2. DNS 解析:在域名管理后台添加 A 记录,把域名指向服务器公网 IP
  3. ICP 备案:国内服务器必须备案,不备案 80/443 端口会被云厂商封掉
    • 在服务器所在云厂商后台提交,一般审核 7-20 个工作日
    • 准备:身份证正反面、域名证书、网站负责人核验照片、真实性承诺书
  4. 公安备案:ICP 备案通过后 30 天内,去"全国公安互联网站安全管理服务平台"再备案一次

小贴士:

  • 赶时间可先用香港或海外服务器,不需要备案;但国内用户访问延迟会高 50-100ms
  • 正式运营的项目,老老实实备案,不然后面麻烦更多

第七步:上线检查清单

部署完别急着收工,按清单逐条检查:

✅ docker compose ps → 所有服务 running ✅ 浏览器访问 https://your-domain.com → 页面正常 ✅ /admin/ 后台 → 能正常登录 ✅ curl https://your-domain.com/api/ → 返回正常 JSON ✅ 静态文件 → CSS/JS/图片正常(F12 看 Network) ✅ 图片上传 → 功能正常,图片能显示 ✅ HTTPS 证书 → 浏览器地址栏有绿色锁 ✅ HTTP 跳转 → 输入 http:// 自动跳 https:// ✅ 数据持久化 → docker compose restart,数据还在 ✅ .env 文件 → 确认没提交到 Git ✅ 日志查看 → docker compose logs -f web 能看到实时日志

部署顺序与踩坑经验

第一次上线,部署顺序很重要。别一上来就docker compose up,按这个顺序来:

1. 服务器基础配置(防火墙、SSH 密钥登录、系统更新) 2. 安装 Docker + Docker Compose 3. git clone 拉代码 4. 配置 .env 环境变量 5. docker compose build 构建镜像 6. 先启动数据库和缓存:docker compose up -d mysql redis 7. 健康检查通过后执行迁移:docker compose exec web python manage.py migrate 8. 创建管理员:docker compose exec web python manage.py createsuperuser 9. 收集静态文件:docker compose exec web python manage.py collectstatic --noinput 10. 申请 SSL 证书 11. 启动全部服务:docker compose up -d 12. 逐项检查上线清单

常见踩坑

坑 1:应用启动后数据库连接失败

应用启动太快,MySQL 还没初始化好。compose 里配了 healthcheck +condition: service_healthy基本可以避免。还有问题就在应用启动脚本加个重试逻辑。

坑 2:静态文件 404

两种可能:忘了执行collectstatic,或 Nginx 的 alias 路径和卷映射对不上。进容器ls /app/static/看有没有文件,再检查 nginx.conf 里 alias 的路径。

坑 3:HTTPS 证书无效或不受信任

可能是域名解析没生效(DNS 缓存最长等 48 小时),或证书路径写错。用openssl s_client -connect your-domain.com:443查看实际返回的证书。

坑 4:Gunicorn worker 频繁重启

先docker stats看内存够不够。2G 内存跑 4 个 worker 可能不够,减到 2 个。再检查 timeout——有些接口执行超过 120 秒需要调大。


AI 加速:让 AI 帮你写部署脚本和排错

部署和运维的活,AI 特别擅长。三个实操场景,亲测好用。

场景 1:生成一键部署脚本

“我有一个 Django 4.2 项目,目录结构如下:[贴 tree 输出]。服务器是 Ubuntu 22.04,已装 Docker 24.0。请生成一个一键部署 shell 脚本,包含:环境检查、代码拉取、镜像构建、数据库迁移、静态文件收集、SSL 证书申请、服务启动。要求有错误处理、彩色日志输出、每一步都有提示。”

几分钟生成一个完整 bash 脚本,比手写调试快太多。

场景 2:排查线上问题

“我的 Django Docker 部署后,访问 API 返回 502 Bad Gateway。Nginx 错误日志:[贴日志]。docker compose ps 输出:[贴输出]。容器内 gunicorn 日志:[贴日志]。请分析原因并给出修复方案。”

AI 能快速定位根因——可能是 worker 全挂、端口映射错误、内存不足被 OOM kill。

场景 3:生成监控告警脚本

“帮我写一个 Docker 健康监控脚本。每 5 分钟检查所有容器状态,如果 web 容器异常则自动重启。连续重启 3 次仍失败就发告警邮件。用 bash 编写,日志记录到 /var/log/shop-monitor.log。”

这种运维脚本手写要半小时,AI 几分钟搞定,质量还高。


学完来练练手

  1. 日志管理:配置 Nginx 的 access_log / error_log 输出到独立目录,用logrotate做日志轮转,防止磁盘撑爆
  2. 可视化监控:部署 Portainer 做 Docker 可视化管理,部署 Watchtower 实现镜像自动更新
  3. 负载均衡:修改 docker-compose 启动 2 个 web 实例,配置 Nginx upstream 的轮询和权重策略
  4. 自动备份:写定时脚本,每天凌晨mysqldump备份数据库,上传到远程 OSS 存储

Docker 官方文档:docs.docker.com;Gunicorn 部署 Django 官方指南:docs.djangoproject.com;Let’s Encrypt。

本系列为梅雅达编程笔记原创,首发于 CSDN。有问题?评论区见 🍃