Python全栈实战:Docker Compose + Nginx 部署 FastAPI、Vue3 与 MySQL
Python全栈实战Docker Compose Nginx 部署 FastAPI、Vue3 与 MySQL完成一个全栈项目的开发只是第一步将它真正部署到生产环境并稳定运行才是更大的挑战。本文将带你从零开始使用Docker Compose和Nginx将FastAPI后端、Vue3前端和MySQL数据库部署为一个完整的生产级应用。这不仅是一个教程更是一套可复用的部署模板。一、最终架构概览浏览器 │ │ 80端口 ▼ Nginx反向代理 静态资源服务 │ ├── /api/* ──────► FastAPI:8000后端API ├── /static/* ───► Nginx直接返回静态文件 └── /* ──────────► Vue3前端静态页面 │ ▼ MySQL:3306数据库 │ ▼ Docker Volume数据持久化这套架构的核心设计思想Nginx作为统一入口处理静态资源、反向代理和负载均衡FastAPI只负责业务逻辑和API不处理静态文件Vue3编译为纯静态文件由Nginx直接服务MySQL通过Docker Volume持久化数据确保容器重启不丢失二、项目结构project/ ├── backend/ │ ├── app/ │ │ ├── __init__.py │ │ ├── main.py # FastAPI入口 │ │ ├── models.py # SQLAlchemy模型 │ │ ├── schemas.py # Pydantic模型 │ │ ├── crud.py # 数据库操作 │ │ ├── database.py # 数据库连接 │ │ └── routers/ # API路由 │ │ ├── __init__.py │ │ ├── auth.py # 认证路由 │ │ └── users.py # 用户路由 │ ├── requirements.txt │ ├── Dockerfile │ └── alembic/ # 数据库迁移 ├── frontend/ │ ├── src/ │ ├── package.json │ ├── vite.config.ts │ ├── Dockerfile │ └── nginx.conf # 前端Nginx配置 ├── nginx/ │ └── nginx.conf # 主Nginx配置 ├── docker-compose.yml └── .env三、FastAPI后端实现3.1 数据库连接# backend/app/database.pyfromsqlalchemyimportcreate_enginefromsqlalchemy.ext.declarativeimportdeclarative_basefromsqlalchemy.ormimportsessionmakerimportos DATABASE_URLos.getenv(DATABASE_URL,mysqlpymysql://user:passworddb:3306/app_db)enginecreate_engine(DATABASE_URL,pool_size20,max_overflow40,pool_pre_pingTrue,# 自动检测连接是否有效pool_recycle3600,# 每小时回收连接)SessionLocalsessionmaker(autocommitFalse,autoflushFalse,bindengine)Basedeclarative_base()defget_db():dbSessionLocal()try:yielddbfinally:db.close()3.2 用户模型# backend/app/models.pyfromsqlalchemyimportColumn,Integer,String,Boolean,DateTimefromsqlalchemy.sqlimportfuncfrom.databaseimportBaseclassUser(Base):__tablename__usersidColumn(Integer,primary_keyTrue,indexTrue)usernameColumn(String(50),uniqueTrue,indexTrue,nullableFalse)emailColumn(String(100),uniqueTrue,indexTrue,nullableFalse)hashed_passwordColumn(String(255),nullableFalse)is_activeColumn(Boolean,defaultTrue)is_superuserColumn(Boolean,defaultFalse)created_atColumn(DateTime(timezoneTrue),server_defaultfunc.now())updated_atColumn(DateTime(timezoneTrue),onupdatefunc.now())3.3 JWT认证# backend/app/routers/auth.pyfromdatetimeimportdatetime,timedeltafromfastapiimportAPIRouter,Depends,HTTPException,statusfromfastapi.securityimportOAuth2PasswordBearer,OAuth2PasswordRequestFormfromjoseimportJWTError,jwtfrompasslib.contextimportCryptContextfrompydanticimportBaseModel routerAPIRouter(prefix/auth,tags[认证])# 配置SECRET_KEYos.getenv(SECRET_KEY,your-secret-key-change-in-production)ALGORITHMHS256ACCESS_TOKEN_EXPIRE_MINUTES30pwd_contextCryptContext(schemes[bcrypt],deprecatedauto)oauth2_schemeOAuth2PasswordBearer(tokenUrl/api/auth/login)classToken(BaseModel):access_token:strtoken_type:strclassTokenData(BaseModel):username:str|NoneNonedefcreate_access_token(data:dict,expires_delta:timedelta|NoneNone):to_encodedata.copy()expiredatetime.utcnow()(expires_deltaortimedelta(minutes15))to_encode.update({exp:expire})returnjwt.encode(to_encode,SECRET_KEY,algorithmALGORITHM)router.post(/login,response_modelToken)asyncdeflogin(form_data:OAuth2PasswordRequestFormDepends(),db:SessionDepends(get_db)):userdb.query(User).filter(User.usernameform_data.username).first()ifnotuserornotpwd_context.verify(form_data.password,user.hashed_password):raiseHTTPException(status_codestatus.HTTP_401_UNAUTHORIZED,detail用户名或密码错误,headers{WWW-Authenticate:Bearer},)access_tokencreate_access_token(data{sub:user.username},expires_deltatimedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES))return{access_token:access_token,token_type:bearer}asyncdefget_current_user(token:strDepends(oauth2_scheme),db:SessionDepends(get_db)):credentials_exceptionHTTPException(status_codestatus.HTTP_401_UNAUTHORIZED,detail无法验证凭据,headers{WWW-Authenticate:Bearer},)try:payloadjwt.decode(token,SECRET_KEY,algorithms[ALGORITHM])username:strpayload.get(sub)ifusernameisNone:raisecredentials_exceptionexceptJWTError:raisecredentials_exception userdb.query(User).filter(User.usernameusername).first()ifuserisNone:raisecredentials_exceptionreturnuser3.4 FastAPI应用入口# backend/app/main.pyfromfastapiimportFastAPIfromfastapi.middleware.corsimportCORSMiddlewarefrom.routersimportauth,users appFastAPI(title全栈应用API,version1.0.0,docs_url/api/docs,redoc_url/api/redoc,)# CORS配置app.add_middleware(CORSMiddleware,allow_origins[*],# 生产环境应限制具体域名allow_credentialsTrue,allow_methods[*],allow_headers[*],)# 注册路由app.include_router(auth.router,prefix/api)app.include_router(users.router,prefix/api)app.get(/api/health)asyncdefhealth_check():return{status:healthy,version:1.0.0}3.5 后端Dockerfile# backend/Dockerfile FROM python:3.12-slim WORKDIR /app # 安装系统依赖 RUN apt-get update apt-get install -y --no-install-recommends \ gcc \ default-libmysqlclient-dev \ rm -rf /var/lib/apt/lists/* # 安装Python依赖 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 # 使用uvicorn运行 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000, --workers, 4]四、Vue3前端4.1 Vite配置// frontend/vite.config.tsimport{defineConfig}fromviteimportvuefromvitejs/plugin-vueimport{resolve}frompathexportdefaultdefineConfig({plugins:[vue()],resolve:{alias:{:resolve(__dirname,src),},},server:{port:5173,proxy:{/api:{target:http://localhost:8000,changeOrigin:true,},},},build:{outDir:dist,assetsDir:assets,sourcemap:false,rollupOptions:{output:{manualChunks:{vue-vendor:[vue,vue-router,pinia],ui-vendor:[element-plus],},},},},})4.2 前端Dockerfile多阶段构建# frontend/Dockerfile # 阶段1构建 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . RUN npm run build # 阶段2生产运行 FROM nginx:alpine COPY --frombuilder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]4.3 前端Nginx配置# frontend/nginx.conf server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # Vue Router history模式 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存 location /assets { expires 1y; add_header Cache-Control public, immutable; } # API代理 location /api/ { proxy_pass http://backend:8000/api/; 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; } }五、Docker Compose编排# docker-compose.ymlversion:3.8services:# MySQL数据库db:image:mysql:8.0container_name:app_dbrestart:alwaysenvironment:MYSQL_ROOT_PASSWORD:${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE:${MYSQL_DATABASE}MYSQL_USER:${MYSQL_USER}MYSQL_PASSWORD:${MYSQL_PASSWORD}ports:-3306:3306volumes:-mysql_data:/var/lib/mysql-./db/init.sql:/docker-entrypoint-initdb.d/init.sqlhealthcheck:test:[CMD,mysqladmin,ping,-h,localhost]interval:10stimeout:5sretries:5networks:-app_network# FastAPI后端backend:build:context:./backenddockerfile:Dockerfilecontainer_name:app_backendrestart:alwaysenvironment:DATABASE_URL:mysqlpymysql://${MYSQL_USER}:${MYSQL_PASSWORD}db:3306/${MYSQL_DATABASE}SECRET_KEY:${SECRET_KEY}depends_on:db:condition:service_healthynetworks:-app_network# Vue3前端 Nginxfrontend:build:context:./frontenddockerfile:Dockerfilecontainer_name:app_frontendrestart:alwaysports:-80:80depends_on:-backendnetworks:-app_networkvolumes:mysql_data:driver:localnetworks:app_network:driver:bridge六、部署与运维6.1 启动服务# 构建并启动所有服务docker-composeup-d--build# 查看运行状态docker-composeps# 查看日志docker-composelogs-fbackend# 执行数据库迁移docker-composeexecbackend alembic upgradehead6.2 健康检查与监控# 健康检查端点curlhttp://localhost/api/health# 监控容器资源使用dockerstats# 查看数据库连接docker-composeexecdb mysql-uuser-p-eSHOW PROCESSLIST6.3 备份策略# 备份MySQL数据docker-composeexecdb mysqldump-uuser-papp_dbbackup_$(date%Y%m%d).sql# 备份Docker Volumedockerrun--rm-vapp_mysql_data:/data-v$(pwd):/backup alpinetarczf /backup/mysql_backup.tar.gz-C/data.6.4 生产环境优化清单启用HTTPS使用Let’s Encrypt Certbot自动获取SSL证书限流保护在Nginx中配置rate limiting日志轮转配置Docker日志驱动为json-file并设置max-size资源限制在docker-compose.yml中设置CPU和内存限制秘密管理使用Docker Secrets或外部密钥管理服务监控告警集成Prometheus Grafana七、常见问题排查问题1后端无法连接数据库# 检查数据库是否就绪docker-composeexecbackend python-c import pymysql conn pymysql.connect(hostdb, useruser, passwordpassword, databaseapp_db) print(连接成功) conn.close() 问题2前端API请求404检查Nginx的proxy_pass配置确保URL路径正确映射。特别注意proxy_pass末尾的斜杠。问题3容器内存泄漏使用docker stats监控内存使用在docker-compose.yml中设置mem_limit和mem_reservation。八、总结通过Docker Compose Nginx的部署方案我们实现了一套完整的生产级全栈应用部署架构。这套方案的核心优势在于环境一致性Docker确保开发、测试、生产环境完全一致一键部署docker-compose up -d即可启动整个应用栈服务隔离每个服务在独立容器中运行互不干扰水平扩展通过docker-compose up -d --scale backend3轻松扩展后端实例数据持久化Docker Volume确保数据在容器重启后不丢失这套方案可以直接作为新项目的部署模板只需替换业务代码和配置即可快速上线。

相关新闻

学术研究的“瑞士军刀”:宏智树AI全流程科研平台深度解析

学术研究的“瑞士军刀”:宏智树AI全流程科研平台深度解析

如果你正在写论文,无论是本科毕业论文还是硕士研究,大概都经历过这样的场景:开题报告用一个模板,文献综述靠手动翻知网,数据分析打开SPSS对着教程一步步操作,查重又要切换到另一个平台——每个环节都要折腾…

2026/11/3 4:25:44 阅读更多 →
一场关于“源头”的反思:当AI让数据治理回归业务本质(

一场关于“源头”的反思:当AI让数据治理回归业务本质(

上周,托山东CIO联盟的福,旁听了一场线上闭门交流,主题挺硬核——“AI赋能的源端数据质量改造”。老实说,刚开始我内心毫无波澜,甚至想嗑瓜子。毕竟“数据治理”这四个字,耳朵都听出茧子了。这些年&#xff…

2026/11/2 14:53:06 阅读更多 →
面试问权限日志可观测,我才知道Demo和上线之间差了多少

面试问权限日志可观测,我才知道Demo和上线之间差了多少

聊《别急着重做程序员就业,先看岗位到底在筛什么》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要摘要:2026年的大模型岗位招聘,已经过了"会调API就能上岗"的阶段。…

2026/11/1 15:56:44 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/3 1:09:25 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/3 10:57:27 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/3 11:35:49 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/3 2:27:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/3 10:47:27 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/3 10:57:33 阅读更多 →

日新闻

周新闻

月新闻