ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Docker+Nginx部署Python Web应用:从Dockerfile到反向代理实战

Docker+Nginx部署Python Web应用:从Dockerfile到反向代理实战 把本地跑得好好的 Python Web 项目丢上服务器结果一堆环境问题、端口冲突、进程守护、静态文件失效轮番轰炸这种经历我太熟了。用 Docker 打包应用、再用 Nginx 做反向代理和静态资源服务是目前最省心、最容易复现的一套方案能管五年不落伍。这篇博文我完整记录一次 Python Web 应用上线服务器的全过程从 Dockerfile 编写、Compose 编排、Nginx 反代配置到常见坑排查全部用真实踩过的细节说话适合刚接手服务器部署的开发者也适合想把原来手动部署那套旧流程升级成容器化方案的老手参考。1. 部署方案整体设计为什么非要用 Docker 加 Nginx1.1 先搞清楚这套组合到底解决了什么问题如果你是第一次把 Python Web 应用部署到服务器可能想不通明明在本地python app.py就能跑为什么不能直接在服务器上复制同样命令完事答案是——能但你会在后续几个月里被各种环境问题折磨到怀疑人生。我在早期部署爬虫管理后台时就吃过这个亏。项目依赖 Python 3.8 的某个特性服务器上装的是 3.10语法不兼容直接跑不起来后来换了台干净机器又发现缺少系统级依赖库编译时各种报错。最要命的是之前手动部署的另一个 Flask 项目占用了 5000 端口新项目上来必须改端口改完还要处理进程守护、日志切割、异常重启……每一次涉及环境变动都等于把整个部署过程重做一遍。Docker 解决的核心问题就是环境一致性。项目里面写一个 Dockerfile把 Python 版本、系统依赖、pip 依赖、启动命令全部固化成一个镜像无论哪台服务器、哪个同事拉下来docker run跑出来的行为完全一样。这种确定性的价值在项目多了之后体现得特别明显。Nginx 解决的则是另外两类问题一是反向代理把公网的 80/443 端口流量转发到容器内部的 Web 服务端口同时天然支持负载均衡和多域名复用二是静态资源处理Python Web 框架处理 HTML 模板和动态请求没问题但高并发下由 Python 进程直接返回图片、CSS、JS 文件既不经济也没必要Nginx 处理这些静态文件消耗资源极低、性能极好。1.2 整体架构长什么样一个请求从外到内怎么流转先看最终部署完成后的架构图无图表纯文字描述请自行脑补用户浏览器 ↓ 请求 example.com Nginx 反向代理服务器宿主机监听 80/443 ├── 请求静态资源/static、/media、图片等→ Nginx 直接返回 └── 动态请求/、/api、/admin 等 → 反向代理到 localhost:8000 → Docker 容器内的 Gunicorn 工作进程 → 调用 Flask/Django/FastAPI 应用逻辑 → 操作 PostgreSQL/MySQL/Redis 容器 → 返回响应给 Nginx → 回到浏览器这里有一个很多人误解的点Nginx 到底应该装在宿主机还是也跑在 Docker 容器里我的建议是如果服务器是单机场景Nginx 直接装在宿主机如果整个基础设施包括 Nginx 都要容器化比如要用 Docker Compose 管理所有服务Nginx 可以放容器里并配置端口映射。两种方式我在生产环境都实测过单机场景宿主机装 Nginx 的好处是无论业务容器怎么重建、崩溃、替换Nginx 本身不受影响对外服务不会断。毕竟浏览器连的是 Nginx只要 Nginx 存活用户就不会察觉到后端容器崩了。而 Compose 里多个容器属于同级重启关系一旦做整体更新中间会有一个窗口期对外不可访问。1.3 Flask、Django、FastAPI 项目在方案选型上的差异标题写的是“Python Web 应用”落地时框架选择会影响一些细节但整体方案框架一致。简单说下三个主流框架在部署时的差异Flask轻量常常是单文件或小型工程。部署时重点是把开发服务器换掉用 Gunicorn 跑配 1-4 个 worker 就足够镜像体积可以压得很小。Django自带 ORM、Admin、静态文件收集机制部署时多两个环节——python manage.py collectstatic收集静态文件到统一目录以及配置数据库连接时注意宿主机和容器的地址差异。重量级项目建议镜像里包含系统依赖编译工具链。FastAPI性能好Gunicorn 作为进程管理器后面挂 Uvicorn 作为 ASGI worker。启动命令和 Flask 略有区别其余环节通用。无论哪个框架部署思维的转变是一样的从“在一个环境里跑起来”转变为“把应用、依赖、配置打包成一个不可变产物在任何地方以相同方式运行”。2. 从零开始服务器环境准备与容器化改造流程2.1 服务器基础配置和三组关键命令服务器推荐 2 核 4G 起步操作系统选 Ubuntu 22.04 LTS 或 Debian 12 这一类主流发行版。初次登录建议先做三件事# 1. 更新软件源和系统 sudo apt update sudo apt upgrade -y # 2. 安装 Docker curl -fsSL https://get.docker.com | bash sudo systemctl enable docker sudo systemctl start docker # 3. 创建一个非 root 的部署账号安全考虑 sudo useradd -m -s /bin/bash deploy sudo usermod -aG docker deployDocker 安装完成后务必验证sudo docker run hello-world如果能正常打印 Hello from Docker说明 Docker 引擎和内核的容器支持都开启了。这里提一个绝大多数人踩过的坑Windows 上装 Docker Desktop 如果碰到 “Virtualization support not detected” 或 “Docker Desktop failed to start because virtualization support is not enabled”十有八九是 BIOS 里的虚拟化开关没打开或者 Windows 的 WSL2 没启用。修复方式是进 BIOS 开启 Intel VT-x/AMD-VWindows 侧在“启用或关闭 Windows 功能”里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”然后重启再试。服务器上的部署账号建好后后续所有操作都用这个账号执行避免直接用 root 带来安全风险。如果 Docker 组生效要等新终端当前终端退出重登即可。2.2 编写一个可上生产的 Dockerfile逐行讲透Dockerfile 是整个容器化方案里最核心的文件写得好不好直接决定镜像体积和构建速度。分享一个我在生产环境实测过的 Flask 项目 Dockerfile后面逐步讲解# 基础镜像 FROM python:3.11-slim-bookworm # 环境变量 ENV PYTHONDONTWRITEBYTECODE1 \ PYTHONUNBUFFERED1 \ PIP_NO_CACHE_DIR1 \ TZAsia/Shanghai # 系统依赖 RUN apt-get update apt-get install -y --no-install-recommends \ build-essential \ libpq-dev \ curl \ rm -rf /var/lib/apt/lists/* # 工作目录 WORKDIR /app # 先拷贝依赖清单再拷贝源码利用 Docker 缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 创建非 root 用户 RUN useradd -m -u 1000 appuser chown -R appuser:appuser /app USER appuser # 暴露端口 EXPOSE 8000 # 启动命令 CMD [gunicorn, wsgi:app, --bind, 0.0.0.0:8000, --workers, 2, --timeout, 60]几个关键点逐个讲基础镜像选用python:3.11-slim-bookworm。不要用默认的python:3.11基于 Debian 完整版它的体积约 1GB而 slim 版本不到 200MB。也不要追求极致用alpine除非你很熟悉 musl libc 的兼容性问题——很多 Python 轮子在 alpine 上需要现场编译反而更慢更麻烦。先COPY requirements.txt再COPY .这里是利用 Docker 构建缓存的关键技巧。Docker 是按照指令逐层构建的每一层都会判断输入文件是否变化。依赖清单一般很少变动源码却天天改。如果先拷贝全部源码再执行 pip 安装那么每次源码变动都会导致依赖层缓存失效pip 全部重新安装白白消耗几分钟构建时间。反过来依赖层稳定不动构建速度能提升 80% 以上。创建普通用户appuser并以该身份运行应用是为了容器安全兜底。容器默认以 root 运行一旦应用存在漏洞被攻击者拿到执行权限就等于拿到了宿主机的 root。换成普通用户后可以大幅降低爆破风险。启动命令用 Gunicorn 而不是python app.py。Flask 自带的开发服务器在并发稍高时就会瓶颈而且官方明确警告不能用于生产。Gunicorn 是 Python 界最成熟的应用服务器配合 sync worker 在大多数业务场景下都够用。worker 数量公式一般参照 CPU 核数2 核机器配 2 个 worker 是理智选择不要盲目堆多——Gunicorn 的多 worker 是进程级并发太多反而会因为内存占用过高导致 OOM。2.3 依赖管理requirements.txt 的生成技巧和环境隔离requirements.txt 是容器化部署中最容易被忽视的环节。很多人图省事直接pip freeze requirements.txt结果把一个全局 Python 环境的所有依赖全部导出来里面包含大量与项目无关的包导致镜像臃肿、可能出现不可预期的覆盖。正确的做法是用虚拟环境重新生成python -m venv venv source venv/bin/activate pip install flask gunicorn psycopg2-binary requests pip freeze requirements.txt还有一个坑是依赖的版本锁定。有些包在 pip 解析时能装下来但跨 PyPI 历史版本可能有兼容问题。个人经验是关键依赖Web 框架、数据库驱动、Gunicorn 这些锁定大版本号比如Flask3.0.*辅助依赖放宽到范围。这样既保证可复现性又不至于因为旧版本漏洞被迫立刻升级。如果项目复杂依赖中有部分包需要特定系统库支持比如lxml需要libxml2-devPillow需要libjpeg-dev记得在 Dockerfile 的 apt-get 环节一并安装。少了系统依赖pip 装包时会直接编译报错而且报错信息很不友好经常会卡在“Failed building wheel for xxx”让人误以为是包本身的问题。2.4 构建镜像实操命令、验证手段和常见失败Dockerfile 写好后在项目根目录执行docker build -t myapp:1.0.0 .构建完成后用以下命令检查镜像和容器开始状态# 查看镜像列表和大小 docker images | grep myapp # 本地启动容器测试 docker run -d --name myapp-test -p 8000:8000 myapp:1.0.0 # 查看日志 docker logs -f myapp-test # 进入容器内部检查运行环境 docker exec -it myapp-test bash这一步最容易出现两个问题问题一端口被占用导致无法启动。如果宿主机 8000 端口已被其他进程占用docker run -p 8000:8000会直接报 “port is already allocated”。排查方法sudo lsof -i :8000 sudo kill -9 PID问题二容器秒退。docker run后容器立刻退出用docker logs一看发现是 Python 启动异常——常见原因包括依赖丢失ModuleNotFoundError、数据库连接不上、配置文件路径错误。定位思路是看日志日志是容器排错的第一入口比猜和分析快得多。本地测试通过后再把构建好的镜像推送到镜像仓库Docker Hub 或私有的 Harbordocker tag myapp:1.0.0 yourname/myapp:1.0.0 docker push yourname/myapp:1.0.0服务器上拉取并运行docker pull yourname/myapp:1.0.0 docker run -d --name myapp \ -p 8000:8000 \ --restart unless-stopped \ yourname/myapp:1.0.0--restart unless-stopped是生产环境必须加的参数。它保证容器内部进程崩溃或宿主机重启后容器能自动拉起省掉手动docker start的麻烦。不加这个参数服务器重启一次你的服务就挂一次直到有人登录上去手动启动。3. 用 Docker Compose 编排复杂依赖数据库也一起管起来3.1 从单容器到多服务Compose 的适用场景单容器跑业务进程能解决“应用本身”的部署但实际项目中 Web 应用后面往往还挂着 PostgreSQL、MySQL、Redis。正常情况下这些数据库也是服务的一部分该不该一起容器化我的实践结论中小型项目和开发环境强烈建议用 Docker Compose 把数据库一起管起来大型生产环境数据库单独部署或使用云数据库。原因很直接Compose 把数据库、应用、缓存放在同一个编排文件里启动、更新、回滚都只需要一条命令还能保证每台新机器上拉起一模一样的服务集。大型场景用云数据库则是因为数据可靠性、备份、扩容这些能力不是简单容器能替代的。3.2 一个完整可跑的 docker-compose.yml 和逐段解读拿我的一个 Flask PostgreSQL Redis 项目举例这是真实在生产服务器上运行过的配置version: 3.8 services: db: image: postgres:15-alpine container_name: myapp-db restart: unless-stopped environment: POSTGRES_DB: myapp POSTGRES_USER: myapp_user POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data ports: - 5432:5432 redis: image: redis:7-alpine container_name: myapp-redis restart: unless-stopped volumes: - redisdata:/data ports: - 6379:6379 app: build: context: . image: myapp:1.0.0 container_name: myapp-app restart: unless-stopped depends_on: - db - redis environment: DATABASE_URL: postgresql://myapp_user:${DB_PASSWORD}db:5432/myapp REDIS_URL: redis://redis:6379/0 SECRET_KEY: ${SECRET_KEY} ports: - 8000:8000 volumes: - static_data:/app/staticfiles volumes: pgdata: redisdata: static_data:逐个讲一下容易被误用的配置项环境变量的注入方式。这里用了${DB_PASSWORD}和${SECRET_KEY}占位符。把它们放在同目录的.env文件中DB_PASSWORDYourStrongPassword123! SECRET_KEYYourSecretKeyHere.NET 方式的好处是密码这类敏感信息不写进 Compose 文件和 Dockerfile避免镜像里泄露密钥。这个习惯必须养成——永远不要在生产环境把数据库密码直接写在硬代码里。depends_on的局限性。这个指令只保证容器启动的先后顺序不能保证数据库完全可用。实际运行中常常出现 app 容器先启动、尝试连数据库时 PostgreSQL 还处于初始化阶段于是连接失败退出。depends_on配合restart: unless-stopped后app 容器退出会反复重启等数据库初始化完毕后就能连上。如果想要更严格的过程管理可以用 healthcheck 实现真正依赖等待这里先不展开。数据卷绑定。pgdata:/var/lib/postgresql/data的意思是数据库文件实际存在命名卷pgdata里容器重建甚至删除数据都不会丢。这个配置对数据库容器来说是生死攸关的——我见过有人不加 volume 跑 PostgreSQL更新镜像后整个库数据全没了的惨案。3.3 Compose 实践的完整操作流程在服务器上拉下代码后完整进行一次构建和启动# 1. 进入项目目录 cd ~/apps/myapp # 2. 创建 .env 文件 cp .env.example .env vim .env # 3. 构建并启动所有服务 docker compose up -d --build # 4. 查看所有服务状态 docker compose ps # 5. 查看日志 docker compose logs -f app # 6. 停止所有服务 docker compose down # 7. 停止并删除数据卷慎用会删除所有数据库数据 docker compose down -vdocker compose up -d后面加--build会在启动前自动按 Dockerfile 重新构建镜像这在更新代码后重新部署时特别方便。如果已经构建过了也可以直接docker compose up -d启动默认镜像版本。4. Nginx 反向代理配置域名绑定、HTTPS 和静态资源分离4.1 Nginx 在这个架构中扮演的三重角色把容器拉起来之后现在服务器上有一个运行在 8000 端口的 Flask 应用容器。但用户不可能在浏览器里通过http://服务器IP:8000访问——这既不美观也可能因安全组配置问题无法从外部访问。Nginx 的价值在这里全部体现出来第一反向代理与端口收敛。Nginx 监听 80/443把请求转发到后端容器端口。用户只需要记住域名不需要关心背后服务到底跑在哪个端口。第二静态资源分离。Nginx 对静态文件的处理能力远超 Python 应用服务器。普通的 CSS/JS/图片请求打到 GunicornGunicorn 只能启动一个 Python 进程去读取磁盘文件再塞进 HTTP 响应而 Nginx 处理静态文件时走的是内核级 sendfile 优化性能差异是数量级的。配置得当的话静态资源根本不会进入 Python 应用直接由 Nginx 从宿主机目录返回。第三TLS 终结与安全防护。证书安装、HTTP/2 协议支持、请求大小限制、IP 黑名单、限流等安全能力全部放在 Nginx 层完成应用层可以专注于业务逻辑。4.2 生产级 nginx 站点配置逐段解析以下是一份实测可用的站点配置放置在/etc/nginx/sites-available/myapp然后软链接到sites-enabledserver { listen 80; server_name example.com www.example.com; # 静态资源请求Nginx 直接处理不转发到后端 location /static/ { alias /data/myapp/staticfiles/; expires 30d; add_header Cache-Control public, immutable; } location /media/ { alias /data/myapp/mediafiles/; expires 7d; } # 所有动态请求转发到 Docker 容器的 Gunicorn 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; proxy_connect_timeout 60s; proxy_read_timeout 120s; proxy_send_timeout 120s; client_max_body_size 50M; } }这份配置有四个关键细节第一个细节是alias和location的搭配。如果请求路径是/static/css/style.cssalias /data/myapp/staticfiles/会把请求映射到宿主机文件/data/myapp/staticfiles/css/style.css。这里如果用root /data/myapp/staticfiles/会变成查找/data/myapp/staticfiles/static/css/style.css逻辑不对。alias是替换前缀root是拼接前缀这是 Nginx 新手最容易踩的坑。第二个细节是静态资源的 cache 头。expires 30d和Cache-Control: public, immutable告诉浏览器这个文件可以放心缓存 30 天。在做前端发布时文件名里一般会带哈希值所以可以大胆长期缓存。这个配置能显著降低服务器带宽和 Python 进程压力。第三个细节是location /里反向代理头。X-Real-IP和X-Forwarded-For让后端应用知道真实客户端 IP否则后面接日志分析、风控接口、用户地域统计时拿到的全是 Nginx 内网地址。X-Forwarded-Proto是给 Django 这类框架用的它以此判断当前请求是 HTTP 还是 HTTPS影响request.is_secure()的结果。第四个细节是超时和上传大小设置。Gunicorn 里面默认 timeout 是 30 秒Nginx 的proxy_read_timeout设置 120 秒整体链路才不会有短连接被杀的情况。client_max_body_size不设置默认只有 1M上传图片稍微大点就直接返回 413 错误。4.3 Nginx 的 location 匹配工作流规则和坑热词榜单里有“nginx 中 location 工作流机制”这也是配置里最容易出问题的点。Nginx 的 location 是按照规则的优先级顺序匹配的而不是从上到下依次尝试。优先级从高到低如下精确匹配location /只匹配根路径。前缀匹配加^~location ^~ /static/命中后立即停止搜索。正则匹配location ~ /\.(?!well-known).*按书写顺序匹配匹配即停止。普通前缀匹配location /是兜底的任何没被上面规则匹配的路径最终都会落到这里。实际部署中经常遇到的现象把/static/写在location /的下面结果 Nginx 始终把静态资源请求转发到后端 Gunicorn导致页面 CSS 全部加载失败浏览器报 404 或 500。原因就是location /是前缀匹配的兜底虽然按顺序它在下面但前缀匹配的规则选择不是按配置文件顺序来的而是按匹配长度来定——/static/比/前缀更长、更精确所以理应命中/static/。但如果location /static/和location /的匹配优先级冲突或者存在正则 location 干扰就很容易出现预期之外的转发。排查 location 命中问题时最好的方法是在 tail -f /var/log/nginx/access.log 里观察请求到底走到了哪个 location。访问一个静态文件如果日志显示该请求被转给了 127.0.0.1:8000说明你的静态规则没生效。5. 上线后常见问题与排查技巧实录这一节把我在生产服务器上实际遇到过的典型问题整理成速查表每一个都对应过真实的脏活累活。现象常见原因排查方法解决方案容器启动后马上退出Python 启动异常、依赖缺失、环境变量未注入docker logs container看最后几行按报错补依赖或调整启动命令浏览器访问 502 Bad GatewayNginx 连不上后端 Gunicorncurl http://127.0.0.1:8000测后端是否通检查容器状态、端口映射、Nginx proxy_pass 地址浏览器访问 504 Gateway Timeout后端处理超时或慢 SQL 拖垮 worker看docker logs有无超时记录检查慢查询调大 Nginx 和 Gunicorn 的超时参数静态文件 404 且 Nginx 日志显示请求转给 Pythonlocation 匹配失误或静态文件未收集到指定目录curl -I http://域名/static/css/style.css修复 alias/root 配置或重新执行 collectstatic表单上传文件提示 413 Request Entity Too LargeNginx client_max_body_size 过小tail -f /var/log/nginx/error.log调大 client_max_body_size 并 reload日志时间显示 UTC 跟本地差 8 小时容器内未设置时区date在容器里执行确认Dockerfile 加TZAsia/Shanghai容器反复重启CrashLoopBackOff启动命令异常或依赖停止服务docker inspect看退出码docker logs看报错修复应用代码或调整 depends_on5.1 数据库连接失败最容易让新人崩溃的环境差异问题本地运行 Flask 项目时数据库地址一般写localhost:5432上 Docker 后代码在容器里跑localhost指向容器自身而不是宿主机上的数据库。如果数据库是 Compose 中的服务连接地址必须改为服务名db如果数据库跑在宿主机上则要用宿主机在 Docker 网桥中的地址通常是172.17.0.1。我在部署时把数据库连接从“本地地址”改成“Compose 服务名”后还遇到过另一个问题Django 项目的ALLOWED_HOSTS没配上域名导致通过 Nginx 访问时框架拒绝响应报Invalid HTTP_HOST header。这类框架层面的环境差异问题排查思路是从 Nginx 日志往应用日志逐层追不要只盯着一端找原因。5.2 Docker 安装和启动阶段的两个高频故障热词里有 “docker 安装 mysql 失败” 和 “Virtualization support not detected”这两类问题本质上都是 Docker 环境层面的。MySQL 容器启动失败最常见的场景是 MySQL 8.0 的认证插件和旧客户端不兼容报错信息类似Authentication plugin caching_sha2_password cannot be loaded。解决方法是在 MySQL 启动环境变量里设置command: --default-authentication-pluginmysql_native_password另一个场景是数据卷权限问题容器启动后 MySQL 因为无法初始化数据目录而退出排查时先看完整日志——MySQL 通常会直接告诉你是权限问题还是字符集问题。Virtualization support not detected常见于个人电脑上装 Docker Desktop 的场景对应命令docker run hello-world执行失败。除了前面提到的 BIOS 虚拟化开关还有一类情况是验证了一部分虚拟化功能但 WSL2 内核组件未更新需要执行wsl --update5.3 证书部署与 HTTPS从“能访问”到“安全访问”当流量从开发环境走进生产环境HTTPS 是绕不过去的一环。当前主流的做法是用 Let’s Encrypt 免费证书配合自动续期。服务器上安装过不少证书工具个人体验是certbot官方流程最可靠sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.comCertbot 会自动识别 nginx 配置申请证书、部署到正确位置、修改 Nginx 配置加入 SSL 段落然后配置自动续期的 systemd timer。整个过程大约 5 分钟搞定。热词里的 “certum 证书自动部署 ssldun” 我虽然没用过但思路一致——都是 ACME 协议自动申请和续期证书区别只在自动化程度和前端界面。证书部署完成后务必检查三个配置点证书文件的权限不要让 Nginx worker 进程无权限读取server块是否监听了 443 端口以及location /里是否还是继续转发没有因为多了一层 TLS 就拦截掉动态请求。5.4 日志与监控服务器出问题的第一现场容器化部署后日志管理比传统方式更碎片化。但这也是优势所在——日志从应用里全部打到 stdout/stderrDocker 会自动采集。常用操作# 实时查看日志 docker logs -f myapp-app # 查看最近 200 行 docker logs --tail 200 myapp-app # 同时查看 compose 下多个服务的日志 docker compose logs -f等攻不动问题的时候加一个日志轮转配置让 Docker 自动清理历史日志避免日志文件无限膨胀占满磁盘logging: driver: json-file options: max-size: 50m max-file: 55.5 更新发布的优雅流程零停机部署业务上线后总会有新版本要发布。最简单也是最容易出现流量中断的发布方式是docker compose down然后up。这个过程中所有容器停止再重启对外服务会中断几十秒到几分钟业务稍微重要一点就很难接受。推荐使用滚动更新的方式只重建业务容器数据库容器保持不动docker compose build app docker compose up -d --no-deps app--no-deps表示不重启依赖服务db、redis配合 Nginx 的多后端配置可以在先起好新容器、确认健康后再切换 Nginx 流量指向实现真正的零停机。实际操作中小项目用 Compose 的滚动更新已经足够不必上复杂的编排系统。6. 安全加固与上线检查清单6.1 容器和镜像层面怎么才算“安全”镜像本身要精简。用docker images看一下如果一个 Python 应用镜像超过 1GB说明基础镜像太胖或依赖没清理干净。瘦身方向换 slim 基础镜像、删掉不必要的系统包、用--no-cache安装 pip 依赖、多阶段构建构建阶段和运行阶段分离。运行进程要用普通用户。前面 Dockerfile 里创建的appuser就是做这件事。再补充一点数据库容器PostgreSQL、MySQL 官方镜像内部已经默认用非 root 用户运行所以主要关注的是业务容器。不要直接暴露数据库端口。Compose 文件里给 db 写了ports: 5432:5432这等于把数据库完全暴露给外网就算密码强壮也是巨大的风险面。如果业务容器和数据库容器在同一个 Docker 网络中通过服务名访问已经足够不需要端口映射到宿主机。把 ports 段落删掉外网就无法访问数据库端口了。6.2 Nginx 层的安全配置建议生产环境的 Nginx server 块建议增加这几条# 隐藏 Nginx 版本号 server_tokens off; # 只允许 TLS1.2 及以上 ssl_protocols TLSv1.2 TLSv1.3; # 禁止通过 IP 直接访问 server { listen 80 default_server; server_name _; return 444; } # 限制请求体大小防止恶意大包 client_max_body_size 50M;还有两个容易忽略的点一个是开启 Gzip 压缩减少传输体积gzip on; gzip_types text/plain text/css application/json application/javascript image/svgxml; gzip_min_length 1k;另一个是对管理后台这类敏感页面限制 IP 或加 Basic Authlocation /admin/ { proxy_pass http://127.0.0.1:8000; auth_basic Restricted; auth_basic_user_file /etc/nginx/.htpasswd; }6.3 上线前的最终检查清单分享一份我每次上线前都会逐项过一遍的清单整理成文字版方便对照服务器 22 端口是否更换默认端口是否禁用了 root 密码登录是否配置了 SSH 公钥Docker 和 Nginx 是否都配置了开机自启systemctl enable容器是否设了restart: unless-stopped数据库是否挂了持久化 volume备份任务是否定时执行服务启动后访问首页和核心功能接口确认 200 状态码静态文件是否由 Nginx 直接返回验证方式浏览器开发者工具看响应头里的Server是否显示 nginx 而不是 gunicorn域名解析是否已指向服务器公网 IPHTTPS 证书是否申请并自动续期日志轮转是否启用日志目录磁盘空间是否充足防火墙是否只放行 80/443/22 端口这套检查清单看着繁琐但每一项背后都有真实事故教训。省步骤的一时痛快往往换来的是半夜被电话叫起来的痛苦。6.4 备份策略数据库备份和配置备份容器化后备份变得简单了但也更容易被遗忘——因为数据库在“容器里”潜意识里总觉得它不是真实数据。数据库备份最简单可靠的方式是用容器内命令docker exec myapp-db pg_dump -U myapp_user myapp | gzip backup_$(date %Y%m%d).sql.gz配置文件的备份则更简单把 Nginx 配置目录、Docker Compose 文件、.env 模板放在 Git 仓库统一管理服务器故障时重新克隆即可恢复。整个部署过程做完最好把关键命令和版本信息记录下来时间久了记忆会模糊文档才是长期可靠的东西。个人习惯是在服务器上建一个~/deploy_docs.md每次上线更新都追加记录。三个月后回看这些记录比任何“快速入门”教程都更有价值。7. 从这套方案能延伸出去的几个方向完成一次标准化的 Docker Nginx 部署后这套能力的可复用性远超单个项目。几周内在另一台服务器部署了第二个、第三个项目流程几乎完全一样只是 Dockerfile 和 Nginx 配置做了参数化调整平均每个项目从零到线上跑通不超过半小时。如果想继续深入下面的方向是自然延伸CI/CD 流水线Git 仓库加 Webhookpush 后自动触发构建、测试和滚动更新彻底告别手动 ssh 上服务器敲命令的流程。监控与告警容器层面的监控可以用 Prometheus GrafanaWeb 服务的存活检测用 Uptime Kuma 这类轻量工具崩溃自动告警到手机。多容器编排项目多了以后可以研究 Docker Swarm 或 Kubernetes把多个服务器变成一个计算池按需调度容器。私有镜像仓库团队内共享构建好的镜像避免每台服务器重复构建耗时耗流量。部署这件事本质上不是一个“做完就结束”的动作它是一条持续演进的基础设施路。从第一次手工在服务器上跑通 Flask到用 Docker 固化环境再到 Nginx 接手流量管理每一步带来的稳定性提升都是质变级别的。我自己在实战中最深的体会是保持简单优先选择主流成熟的方案。Docker 加 Nginx 这套组合能流行这么多年并不是因为它华丽而是因为它把所有花里胡哨的问题都挡在了外面留下来的每一步都是可控、可排查、可回滚的。遇到陌生报错时先别慌从日志里找线索用docker logs、nginx error.log、journalctl这些顺手工具一层层剥大部分问题都能拆出根源。最后分享一个小技巧上线前先把整个流程在一个临时服务器或本地虚拟机完整跑一遍把卡壳点全部解决完之后再去操作生产服务器。这个习惯我保持至今因为所有“我以为没问题但其实有一堆隐藏坑”的环节都能在这个演练环节暴露出来。等到真正部署时剩下的就是复制成功的路径——这种感觉远比一边修 bug 一边被用户催着上线要踏实得多。
返回列表