尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Docker+Nginx部署Python Web应用:从开发环境到服务器的完整指南

Docker+Nginx部署Python Web应用:从开发环境到服务器的完整指南 把Python Web应用从开发环境搬到服务器是很多初学者迈不过去的一道坎。开发的时候用Flask自带服务器跑得飞起真到了上线这天什么Python版本不一致、依赖装不上、端口被占用、进程一关服务就挂……各种问题全冒出来了。这篇文章要解决的问题就是如何用Docker把Python应用打包成标准镜像配合Nginx做反向代理稳定地跑在云服务器上。这套方案适用于Flask、FastAPI、Django等主流Python Web框架也适用于想要规范部署流程、减少环境踩坑的开发者。我会从方案选型讲起再到环境准备、容器化改造、Nginx配置、完整实操流程和问题排查覆盖一套可以直接照着做的部署路径。1. 部署方案总体拆解1.1 为什么选Docker Nginx而不是直接在服务器上裸跑很多人一开始会想服务器上直接装Python然后把代码clone上去python app.py不就能跑了吗确实能跑但只适合临时演示。裸跑会踩这几个痛点开发机是Windows或macOS服务器是Ubuntu或CentOSPython版本、底层依赖库存在差异本地能启动的项目服务器上一跑就报错。一台服务器上如果跑多个Python应用依赖之间容易互相冲突。你升了一个库的版本另一个应用挂了排查起来非常头疼。进程管理是个大坑。用nohup python app.py 方式启动SSH一断开、进程崩了、服务器重启了服务就没了还得手动去拉起。升级回滚困难。代码改了一版改出问题了想退回上一版裸跑环境根本没做版本管理。Docker把这些痛点一网打尽。镜像就是打包好的运行环境里面固化了Python版本、依赖库、启动命令无论推到哪台服务器跑起来的结果都一样。容器之间相互隔离多应用共存不冲突。镜像用tag做版本管理回滚只是换一个镜像标签的事。配合restart: always策略容器崩了自动拉起服务器重启也会自动恢复。那为什么还要加个Nginx因为Docker解决的是“应用怎么跑”的问题Nginx解决的是“流量怎么进”的问题。你的Python容器默认监听某个端口比如8000如果直接用http://服务器IP:8000访问会遇到几个麻烦一是端口暴露得太多如果一个服务器上跑三个应用总不能让用户记住三个带端口的地址二是80/443端口是Web服务的默认入口让用户敲端口访问非常不专业三是Nginx能做负载均衡、静态文件加速、HTTPS证书终结这些事性能和安全层面都比直接把Python服务暴露到公网更靠谱。所以最终架构是Nginx在容器里监听80/443端口接收所有外部请求再按规则转发到内部的Python应用容器。1.2 整体架构与请求链路先看最简单的一台服务器上的部署结构浏览器 │ ▼ Nginx 容器监听 80/443 端口 │ proxy_pass 转发到内网地址 ▼ Python 应用容器Gunicorn 监听 8000 端口跑 Flask/Django/FastAPI │ ▼ 数据库容器MySQL/PostgreSQL/Redis这里有一个关键点Nginx容器和Python容器应该在同一个Docker网络中它们之间通过服务名互相访问不需要把Python的8000端口暴露到宿主机上。这样外部流量只能通过Nginx进入安全性和规范性都更好。我用一个生活化类比来解释这套架构Docker容器就像一个个小房子应用在房子里跑有自己的小环境Nginx是小区门口的保安亭所有访客先到保安亭登记保安再告诉访客去几栋几单元。没有保安亭访客就得直接跑到每个房子门口敲门既混乱又不安全。这套架构说起来简单但真正落地的时候有很多细节要注意Docker版本怎么装、镜像加速怎么配、Dockerfile怎么写才科学、Nginx反代需要设置哪些请求头、静态文件怎么处理、数据库连接怎么管理等。下面逐个拆解。2. 部署前的服务器与环境准备2.1 服务器选型与基础初始化部署这套方案服务器的配置不用太高。个人项目、小流量Web应用2核4G的云服务器完全够用。操作系统建议选Ubuntu 22.04 LTS或Debian 12这两者在Docker的支持上最省心社区资料也多。服务器到手后先做两个基础动作。第一是更新系统软件包sudo apt update sudo apt upgrade -y第二是创建非root用户用于日常操作。我一直建议不要直接用root跑业务万一某个操作出错影响面太大。创建一个叫deploy的用户并加入sudo组sudo useradd -m -s /bin/bash deploy sudo usermod -aG sudo deploy sudo passwd deploy之后用这个用户登录服务器操作Docker相关的命令需要加sudo。如果你实在嫌麻烦直接把当前用户加入docker组可以免sudo执行docker命令sudo usermod -aG docker deploy改完组要重新登录一次才生效。2.2 安装Docker与Docker ComposeDocker的安装路径有两条一是用官方脚本一把梭二是用apt源安装。我推荐用官方脚本省事版本也新curl -fsSL https://get.docker.com | bash -s docker安装完成后验证一下sudo docker version sudo docker compose version如果你拿到的是旧教程里面写的是docker-compose带横杠命令那是旧版Compose的语法。现在主流版本是Docker Compose v2直接用docker compose空格调用。如果系统提示找不到compose命令多半是Docker版本比较老升级一下即可。国内服务器有一个极度影响体验的问题拉取Docker Hub镜像慢到怀疑人生。解决办法是配置镜像加速器。编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }保存后重启Dockersudo systemctl restart docker配置完加速器后拉镜像的速度会明显提升。这里顺带提一个热搜里很多人踩的坑在Windows上安装Docker Desktop时提示virtualization support not detected说明电脑的CPU虚拟化没有开启需要进BIOS打开Intel VT-x或AMD SVM。这个细节在后续“常见问题”章节会展开说。2.3 基础网络与端口规划部署前先想清楚端口怎么规划。通常占用这几个端口80HTTP入口由Nginx容器占用。443HTTPS入口由Nginx容器占用。3306、6379如果MySQL、Redis也在Docker里跑这些端口通常只对内部网络开放不需要映射到宿主机。如果为了本地调试方便要映射建议只绑定到127.0.0.1。怎么理解“只对内开放”在做端口映射的时候3306:3306和127.0.0.1:3306:3306的区别在于前者所有网络接口都能访问相当于把数据库暴露到公网非常危险后者只有服务器本机localhost能访问外部请求到不了。云服务商控制台的安全组也要放行80、443端口。安全组是云服务器的第一道防火墙在ECS/CVM控制台的“安全组”规则里添加入方向规则端口填80/443来源填写0.0.0.0/0。这一步漏了的话服务器的防火墙无论怎么配外面都访问不到。3. Python项目容器化改造3.1 代码层面需要做哪些调整容器化不是把代码扔进Docker就完事了有些习惯得先改过来。第一所有配置必须在代码外部化。什么是外部化就是DEBUGTrue、SECRET_KEYxxxxx、DATABASE_URLmysql://...这些值不能硬编码在代码文件里要从环境变量读取。比如Flask项目里配置写成import os DEBUG os.getenv(DEBUG, false).lower() true SECRET_KEY os.getenv(SECRET_KEY, please-change-me) DATABASE_URL os.getenv(DATABASE_URL, sqlite:///app.db)为什么要这么做因为同一个镜像可能部署到测试环境和生产环境代码完全一样只是环境变量不同。Docker本身就支持在启动容器时注入环境变量用环境变量管理配置是容器化部署的基本功。第二明确Python依赖。项目根目录放一份requirements.txt尽量锁定大版本号或精确版本号。别偷懒写一堆不带版本号的依赖这次装和下次装可能依赖版本就漂移了正好砸中“开发环境能跑、线上环境跑不起来”的老问题。推荐用pip freeze requirements.txt生成当前环境的依赖列表或者手动整理核心依赖。第三确认Web框架的启动方式。开发时用Flask自带的app.run()或Django的runserver可以生产环境必须换成异步WSGI服务器最常用的是Gunicorn。后面会专门讲。3.2 编写一个科学的Dockerfile以Flask项目为例项目的目录结构大概是myapp/ ├── app/ │ ├── __init__.py │ └── views.py ├── requirements.txt ├── Dockerfile ├── .dockerignore ├── nginx/ │ └── default.conf └── docker-compose.ymlDockerfile的内容FROM python:3.11-slim WORKDIR /app ENV PYTHONDONTWRITEBYTECODE1 \ PYTHONUNBUFFERED1 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8000 CMD [gunicorn, --bind, 0.0.0.0:8000, --workers, 3, app:app]逐行解释一下关键点python:3.11-slimslim版本体积小包含运行Python所需的最小环境。尽量别用带-alpine的版本虽然体积更小但某些依赖库需要编译容易出问题。PYTHONDONTWRITEBYTECODE1不生成__pycache__减少镜像里的垃圾文件。PYTHONUNBUFFERED1日志不缓冲实时输出到标准输出方便用docker logs看日志。pip install -i https://pypi.tuna.tsinghua.edu.cn/simple国内服务器装PyPI依赖用清华镜像速度差距非常大不用镜像可能等十几分钟用了镜像一分钟内装完。CMD里用的是Gunicorn而不是python app.py。Gunicorn是多进程WSGI服务器能利用多核CPU并发能力远超开发服务器。--workers 3表示开3个工作进程一般按“CPU核心数 × 2 1”估算。如果服务器是2核5个worker比较合适我这里写3是为了保守看实际内存调整。另外.dockerignore文件很容易被忽略但非常重要。它的作用是告诉Docker哪些文件不要进入镜像构建上下文。最小内容__pycache__/ *.pyc .git/ .env venv/没有.dockerignoreCOPY . .会把你本地的虚拟环境、git目录、日志文件全打进去镜像又大又慢。3.3 静态文件与运行用户Django项目会用到collectstatic收集静态文件Flask项目如果有独立的前端资源也一样默认由Python处理。但在生产环境更好的方案是让Nginx直接处理静态文件不要经过Python应用。这样Python容器专注于动态请求静态资源响应速度也更快。具体操作是Nginx容器里挂载一份静态文件目录配置里用alias或root指向它。后面Nginx配置部分会给出完整写法。那静态文件怎么进到Nginx容器里的两个思路一是构建时在Nginx镜像里COPY进去二是用Docker的数据卷把宿主机目录共享给两个容器。我常用的是后者在Compose里把宿主机的static/目录同时挂载给Python容器和Nginx容器。还有一个安全细节容器内默认是root用户跑应用如果镜像被打包分发root权限会有安全风险。可以在Dockerfile里创建非root用户并切换RUN addgroup --system app adduser --system --ingroup app app USER app注意如果用了这个配置容器内写文件比如Django的media上传目录所在的数据卷必须给app用户写权限否则会报Permission denied。3.4 docker-compose.yml 编排三件套docker-compose.yml是整套部署的中枢。我个人习惯把Nginx、Python应用、数据库放到同一个Compose文件里管理用服务名互相访问主机映射只暴露Nginx的80端口和数据库的本地端口。一个最简但完整的编排文件services: web: build: . restart: always env_file: - .env volumes: - static_volume:/app/static expose: - 8000 depends_on: - db nginx: image: nginx:1.25-alpine restart: always ports: - 80:80 volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - static_volume:/app/static depends_on: - web db: image: mysql:8.0 restart: always env_file: - .env volumes: - mysql_data:/var/lib/mysql expose: - 3306 volumes: static_volume: mysql_data:逐个解释这里面的设计意图web服务用的是build: .也就是用当前目录的Dockerfile构建镜像。expose和ports的区别要分清。expose只在Docker内部网络中暴露端口宿主机和外部访问不到ports才会把端口映射到宿主机。web服务的8000端口只需要让Nginx容器通过内部网络访问所以用expose不用映射到宿主机。nginx服务把本机的./nginx/default.conf挂载到容器内的Nginx配置目录改配置不用重新构建镜像改完docker compose restart nginx就生效。env_file: .env将环境变量从文件加载进容器数据库密码、SECRET_KEY这类敏感信息都放在.env里不进仓库。depends_on保证启动顺序。但要注意它只保证“先启动”不保证“可用”。MySQL容器启动到真正能接受连接还有几秒到几十秒的初始化时间Python应用如果启动时立即连数据库可能连不上。这个问题在后面的“常见问题”章节会提供一个解决方案。.env文件示例SECRET_KEYyour-secret-key DATABASE_URLmysql://myapp:myapp123db:3306/myapp MYSQL_ROOT_PASSWORDroot123 MYSQL_DATABASEmyapp MYSQL_USERmyapp MYSQL_PASSWORDmyapp123有一个地方容易出错DATABASE_URL里数据库地址写的是db而不是127.0.0.1。因为在Compose网络中服务名db会被DNS解析到MySQL容器的IP地址。如果你写成127.0.0.1Python容器访问的是它自己的回环地址里面并没有MySQL在监听必然连接失败。4. Nginx反向代理配置详解4.1 反向代理到底在做什么先搞清楚正向代理和反向代理的区别。正向代理是“替客户端访问服务器”客户端知道代理的存在访问被限制的资源时找代理帮忙比如常见的开发调试代理。反向代理是“替服务器接收请求”客户端不知道代理的存在它访问的是NginxNginx再转发给后面的应用服务器。在部署场景里Nginx做的是反向代理。用户访问http://你的域名请求到达NginxNginx根据配置把请求转发给内部网络里的Python容器。等Python返回响应Nginx再转回给用户。这个过程对用户完全透明。加一层Nginx带来的实际收益有三个统一入口。多个Python应用可以共用一个80/443端口用不同域名或不同路径区分。安全缓冲。Python应用不需要直接暴露公网端口减少被扫描和攻击的面。性能提升。Nginx的静态文件处理能力和并发连接能力远强于Python应用服务器静态资源交给Nginx能显著降低应用压力。4.2 一份完整的Nginx站点配置在项目目录下创建nginx/default.confupstream myapp { server web:8000; } server { listen 80; server_name example.com www.example.com; client_max_body_size 20M; location /static/ { alias /app/static/; expires 7d; access_log off; } location / { proxy_pass http://myapp; 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 60s; } }关键点拆解upstream myapp { server web:8000; }定义了上游应用服务器池web是Compose里Python服务的名称。如果以后扩展成多实例可以在这里加多行server实现负载均衡。location /static/静态文件请求由Nginx直接处理alias /app/static/指向Nginx容器内挂载的静态文件目录注意alias后面必须有/。expires 7d给静态资源加7天浏览器缓存降低服务器压力。加了access_log off避免静态资源请求刷屏日志。location /其余请求全转发到Python应用。proxy_set_header这几行必不可少特别是X-Forwarded-For如果缺了它Django/Flask拿到的客户端IP全是Nginx容器的IP日志里的真实访客IP就丢了。client_max_body_size 20M允许上传的最大请求体大小。默认值只有1M如果应用有文件上传功能不调大会直接413错误。关于proxy_pass http://myapp和proxy_pass http://web:8000的区别两种写法都能用。用upstream的好处是后期可以在Nginx层做更灵活的负载均衡策略比如加权重、加健康检查。简单场景下直接在proxy_pass里写http://web:8000也行。4.3 启用HTTPS证书没有HTTPS现代浏览器地址栏会提示不安全而且HTTP明文传输时密码、Cookie都能被截获。部署完成后强烈建议上HTTPS。最省事的方案是用Certbot自动申请和续期Let‘s Encrypt免费证书。安装Certbotsudo apt install certbot python3-certbot-nginx然后执行sudo certbot --nginx -d example.com -d www.example.comCertbot会自动识别Nginx配置、自动申请证书、自动改写配置文件加入SSL相关设置还会自动配置HTTP跳转HTTPS。证书90天有效期Certbot会通过systemd定时任务自动续期基本不用手动管。如果你用的是云厂商的免费证书流程是去云控制台申请证书 → 下载Nginx版证书文件 → 上传到服务器 → 手动改Nginx配置。手动配置的关键片段server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; # 其余配置同HTTP版 } server { listen 80; server_name example.com; return 301 https://$host$request_uri; }注意容器里的Nginx需要把证书文件也挂载进去在Compose的nginx服务里加一行volumes: - /etc/nginx/ssl:/etc/nginx/ssl:ro5. 完整实操部署流程5.1 代码上传与目录规划先把项目代码传到服务器。最推荐的方式是用Git在服务器上直接clone仓库。如果项目是私有的需要配置SSH key或者在服务器上使用带token的clone地址。没有Git仓库的话用scp从本地传到服务器也行scp -r ./myapp deploy服务器IP:/home/deploy/www/传到服务器后目录结构应该是/home/deploy/www/myapp/ ├── app/ ├── requirements.txt ├── Dockerfile ├── .dockerignore ├── .env ├── nginx/ │ └── default.conf └── docker-compose.yml注意.env文件里有数据库密码和SECRET_KEY千万不要把这个文件提交到Git仓库。我见过太多把密钥提交进仓库导致被爬虫扫出数据库密码的案例。建议在Git仓库中忽略.env传到服务器后手动创建。5.2 构建并启动容器进入项目目录先检查配置文件语法有没有问题cd /home/deploy/www/myapp docker compose config这个命令会解析Compose文件并输出最终的解析结果。如果报错说明YAML格式有问题或者服务定义有误先修好再往下走。然后构建并启动docker compose up -d --build-d表示后台运行--build表示构建镜像后启动。第一次构建会比较慢因为要拉基础镜像、装依赖。耐心等待构建完成后查看容器状态docker compose ps如果所有服务的状态都是Up说明容器已经跑起来了。此时在服务器本地用curl测试一下curl -I http://127.0.0.1如果返回HTTP/1.1 200 OK说明Nginx已经正常响应。再用完整域名从本地浏览器访问看页面是否正常。数据库迁移也需要执行一次。Django项目docker compose exec web python manage.py migrateFlask项目如果有初始化表结构的命令同理用docker compose exec web进入容器执行。5.3 查看日志与排错容器跑起来了不代表一切正常。查看所有服务的日志docker compose logs -f只看某个服务的日志docker compose logs -f web日志是最直接的排错入口。Python应用启动报错了、数据库连接失败了、Nginx转发超时了都会反映在日志里。日志默认是彩色的-f参数是持续跟踪新日志输出。生产环境我一般会把日志接入到集中式日志平台但个人项目直接用docker compose logs就够了。5.4 更新与发布新版本代码改了要上线最常规的操作是git pull docker compose up -d --build流程是拉取最新代码 → 重新构建镜像 → 优雅替换容器。Gunicorn默认支持优雅重启正在处理的请求会处理完才结束进程不会出现请求中断。如果想零停机更新可以配置deploy.rollback_config或其他蓝绿发布方案但个人项目用上面的简单方案已经足够稳。5.5 数据持久化与备份验证Compose文件里已经为MySQL配置了数据卷mysql_data数据库数据存放在卷里容器删了重建数据不会丢。但这不等于万事大吉卷里的数据如果服务器磁盘坏了同样会丢。建议定期备份MySQL数据docker compose exec db mysqldump -u root -p myapp backup_$(date %F).sql可以把这条命令加到crontab里每天凌晨备份一次备份文件保留最近7天。恢复的时候cat backup_2025-01-01.sql | docker compose exec -T db mysql -u root -p myapp一定要亲手验证一次备份文件能正常恢复。我见过太多人配置了定时备份结果因为密码写错、命令路径不对备份文件全是0字节真正出事的时候才发现根本没备份成功。6. 常见问题与排查技巧实录6.1 502 Bad Gateway这是Nginx部署中最常见的错误意思是Nginx无法连接到上游的Python应用。排查思路按顺序走看web容器是否还在运行docker compose ps如果显示Exit说明Python应用启动失败进web容器日志找原因docker compose logs web。确认web容器内部端口是否监听了docker compose exec web curl -I http://127.0.0.1:8000。如果容器里没有curl可以用Python代替docker compose exec web python -c import urllib.request; print(urllib.request.urlopen(http://127.0.0.1:8000).status)。确认Nginx的upstream配置里的服务名与Compose服务名一致。配置里写的是server web:8000Compose服务名必须是web如果写错了或者忘了在同一个网络里就会502。确认Nginx配置没有语法问题docker compose exec nginx nginx -t。多数情况下问题出在Python应用启动失败导致容器退出日志里会明确提示是缺依赖、端口被占用还是代码报错。6.2 同端口冲突服务器上已经有服务占用80新部署一个项目启动Nginx容器时报bind: address already in use说明宿主机80端口已经被占用了。这种现象很常见之前用裸机部署过Nginx的、服务器面板自带Web服务的都会占80端口。处理办法是先看看谁占了端口sudo lsof -i :80如果是系统自带的Apache或老版本Nginx停掉并禁用开机启动sudo systemctl stop apache2 sudo systemctl disable apache2如果是另一个Docker容器占用了80端口需要检查那个容器的端口映射配置改掉其中一个。6.3 静态文件全部404页面能打开但样式全丢F12看到静态资源返回404。核心原因是静态文件挂载路径和Nginx alias路径对不上。比如Django项目collectstatic后文件在/app/static/Nginx配置里location /static/ { alias /app/static/; }如果 /app/static/ 下没有文件自然404。排查方法docker compose exec nginx ls -la /app/static/如果目录为空回Python容器执行静态文件收集docker compose exec web python manage.py collectstatic --noinput还有一个常见错误alias路径结尾的/没写导致/static/css/style.css请求被映射到/app/staticcss/style.css路径拼接错误。6.4 容器总是自动重启又立刻退出配置了restart: always后容器启动失败会进入无限重启循环。用docker compose ps能看到Restarting状态。此时要做的不是急着改代码而是先关掉自动重启让容器停在该停的地方docker compose stop web然后手动前台启动直接看报错docker compose run --rm web这种方式会把Python应用的stdout直接打印到终端所有启动报错、语法错误、ImportError一目了然。6.5 Python容器启动时连不上MySQL在Compose的depends_on只能保证容器的启动顺序不能保证MySQL就绪。如果Python应用启动时立刻执行连库操作而MySQL还在初始化就会报Cant connect to MySQL server。我常用的解决方式是在启动命令前加一个等待脚本用sh -c组合命令实现。Compose里web服务的命令改成command: sh -c echo Waiting for db... while ! nc -z db 3306; do sleep 1; done echo db is ready gunicorn --bind 0.0.0.0:8000 app:app 如果Python基础镜像里没有nc命令可以用Python实现同样的等待逻辑command: sh -c python -c \import socket, time; s socket.socket(); while True: try: s.connect((db, 3306)); break except Exception: time.sleep(1)\ gunicorn --bind 0.0.0.0:8000 app:app 这种方式比直接依赖depends_on可靠得多。不过从设计上考虑更优雅的姿势是应用在启动时做重试逻辑比如Django的连接池和Retry机制但小项目先跑起来更重要。6.6 Windows上Docker Desktop启动失败搜索热词里好几个都和这个有关virtualization support not detected docker desktop failed to start。这个问题本质是Windows的虚拟化功能没开启。确认路径任务管理器 → 性能 → CPU看“虚拟化”是否显示“已启用”。如果显示“已禁用”重启电脑进BIOS/UEFI设置找到Intel VT-x或AMD SVM设为Enabled。重启后确认Windows功能里Hyper-V和Windows 虚拟机监控程序平台是勾选状态。另外Docker Desktop在旧版Windows 10上需要WSL2支持。建议直接去Docker官网下载最新版Docker Desktop安装包会自动处理WSL2的启用流程。装完如果是新装的系统先重启一次再启动Docker Desktop成功率会高很多。6.7 常见问题速查表现象可能原因快速处理502 Bad GatewayPython容器挂了或网络不通查看web容器状态和日志静态资源404alias路径不对或未执行collectstatic检查挂载路径并收集静态文件80端口被占用其他服务占用host端口停掉占用服务或换端口映射容器无限重启Python启动即崩溃用docker compose run --rm web前台查看报错数据库连接失败MySQL未就绪或地址写错把URL地址改为服务名db并加等待脚本单文件上传超过1M报413Nginx默认body限制太小调大client_max_body_size真实客户端IP丢失缺少X-Forwarded-For头检查proxy_set_header配置6.8 部署完成后还需要做的小事容器全部跑通之后有三件小事容易被忽略但很重要。第一在云控制台配置安全组时只开放必要的端口。SSH端口22可以改成非默认端口或者限制来源IP数据库端口3306绝不要对外开放。安全组宁可少开不要多开不开端口并不影响Docker内部网络的通信。第二为Nginx配置基础安全响应头。在Nginx配置的server块里加几行add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header Referrer-Policy strict-origin-when-cross-origin always;这些响应头能防点击劫持、MIME嗅探等基础Web攻击。Django项目的话强烈建议把SECURE_PROXY_SSL_HEADER (HTTP_X_FORWARDED_PROTO, https)写进配置配合Nginx的X-Forwarded-Proto头让Django知道当前请求是通过HTTPS来的否则Django会一直认为自己处于不安全连接中可能引发重定向死循环。第三确认服务器的时区和系统时间准确。容器日志时间如果差8小时排查问题时会非常难受。设置时区sudo timedatectl set-timezone Asia/Shanghai容器内的时区如果是UTC可以在Compose的web服务里加一个环境变量environment: - TZAsia/Shanghai7. 几个提高运维效率的小技巧7.1 用别名精简Docker命令docker compose命令天天敲太长了。在~/.bashrc里加几个别名alias dcdocker compose alias dcpsdocker compose ps alias dclogdocker compose logs -f alias dcbuilddocker compose up -d --build alias dcedocker compose exec保存后source ~/.bashrc生效。之后dclog web就能查看web服务日志效率高很多。7.2 容器里改代码即时生效开发环境联调时每次改代码都要重建镜像挺痛苦的。如果在Compose的web服务里挂载了源码目录volumes: - .:/app那么修改宿主机代码后容器内部同步变化。配合Gunicorn的--reload参数代码改动后自动重启服务。这个方案只建议开发环境用生产环境千万要关掉否则代码文件被意外改动会影响线上服务。7.3 定期清理无用镜像迭代几轮之后服务器上会堆满旧的镜像和悬空镜像。一条命令清理docker system prune -af-a删除所有未使用的镜像-f跳过确认。注意它会删除所有没有被容器使用的镜像执行前先看一下docker image ls的输出别误删了要用的镜像。7.4 不要让Docker容器跑在坏习惯上有几个坏习惯一定要改容器内不要用apt install装一堆东西。容器是临时的任何手动安装的包在容器重建后都会丢失。正确做法是把安装步骤写进Dockerfile。不要往容器里传密码。密钥、密码通过环境变量或密钥管理服务传入不要写进代码或镜像。不要把数据库数据放在容器可写层。MySQL的/var/lib/mysql必须挂载数据卷否则容器一删数据全没了。8. 一次完整的部署实战记录从零走一遍完整流程这是我实际部署一个Flask博客应用的记录按这个流程操作基本不会卡壳。第一步本地代码整理。确认项目结构干净删掉虚拟环境、缓存文件写出requirements.txt加上.dockerignore。第二步准备服务器。Ubuntu 22.042核4G。执行系统更新安装Docker和Compose配置镜像加速器创建普通用户并加入docker组。第三步上传代码。用Git的方式服务器上git clone项目仓库。创建一个.env文件写入数据库密码、SECRET_KEY等环境变量。第四步构建启动。执行docker compose up -d --build观察构建日志确认依赖安装成功。构建完成后docker compose ps确认三个服务都在运行。第五步初始化数据库。执行docker compose exec web python manage.py migrate确认迁移成功。第六步配置HTTPS。先确保域名解析到服务器IP然后执行certbot --nginx -d example.com按提示完成申请。程序会自动修改Nginx配置并重载。第七步全链路验证。浏览器访问域名确认首页能打开登录后台确认数据库读写正常上传一张图片确认静态文件处理和Nginx body大小配置正常查看docker compose logs web确认没有报错。整个过程大概20分钟前两次做可能踩坑花一两个小时熟悉之后速度会快很多。在实际操作中我自己的体会是部署这件事80%的问题都出在“环境差异”上Docker解决的就是这部分问题但依然有20%的问题出在“配置细节”上比如网络、路径、权限这些只能靠经验和日志来积累。所以遇到问题不要慌先看日志再按网络通路一层一层排查绝大多数问题都能定位。如果这篇文章能帮你把第一次部署顺利跑通那就算没白写。
返回列表