资讯动态

Docker Compose部署Vue+Node+MongoDB全流程与避坑指南

发布时间:2026/10/23 16:31:23 来源:尧图企业网站定制
1. 为什么选择Docker来部署这套Web应用很多开发者第一次接触Docker是因为被环境不一致这个问题折磨得够呛。本地跑得好好的Vue前端、Node后端、MongoDB数据库一放到服务器上就各种报错——Node版本不对、MongoDB连接超时、Nginx配置路径写错。这套组合Vue Node MongoDB是目前非常典型的前后端分离架构也是Docker最能发挥价值的场景之一。Docker解决的核心问题不是让应用跑起来而是让应用在任何地方都以同样的方式跑起来。它把应用和它的运行环境打包成一个镜像镜像里包含了操作系统层、运行时、依赖库、配置文件甚至环境变量。你把这个镜像丢到任何装了Docker的机器上它跑出来的行为都是一致的。这套Web应用的部署涉及三个独立服务前端静态资源、后端API服务、数据库。传统部署方式下你需要手动在服务器上装Node、装MongoDB、配Nginx、设开机自启每一步都可能出问题。用Docker Compose编排之后三个服务通过一个配置文件定义清楚一条命令全部拉起服务之间的网络、依赖关系、数据持久化都由Docker管理。这篇文章适合已经能独立写出Vue前端和Node后端、但对Docker部署还不太熟悉的开发者。我会从镜像构建的细节讲起把每个配置项为什么这么写说清楚再分享几个我在实际部署中踩过的坑。读完之后你应该能独立把这套架构部署到任何一台Linux服务器上。2. 三个服务的镜像构建策略与Dockerfile细节2.1 Vue前端多阶段构建是必选项Vue项目最终产物是静态文件HTML、CSS、JS不需要Node运行时。但构建过程需要Node环境。如果你直接在服务器上装Node然后npm run build会有两个问题一是服务器上多了一套Node环境二是构建产物和构建环境耦合。多阶段构建的思路是第一阶段用Node镜像完成构建第二阶段用Nginx镜像只拷贝构建产物。这样最终镜像里只有Nginx和静态文件体积从几百MB降到几十MB。# 第一阶段构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --registryhttps://registry.npmmirror.com COPY . . RUN npm run build # 第二阶段运行 FROM nginx:1.25-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;]这里有几个细节值得说。npm ci比npm install更适合CI/CD场景它严格按照package-lock.json安装不会因为版本范围导致每次构建结果不同。--registry参数指向国内镜像源构建速度会快很多这个在实际部署中很关键否则npm ci可能卡几分钟。Nginx配置里必须处理Vue Router的history模式。如果前端用了history模式直接访问/user/profile这种路径Nginx会去找对应的文件找不到就404。需要加一段try_filesserver { listen 80; location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend:3000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass后面的backend:3000这里的backend是Docker Compose里定义的服务名Docker内置的DNS会自动解析。这就是为什么用Docker Compose比手动docker run方便——服务之间可以直接用服务名通信不用管IP。2.2 Node后端生产环境镜像的瘦身技巧Node后端的Dockerfile看起来简单但有几个容易忽略的点。首先是NODE_ENVproduction这个环境变量会影响很多库的行为比如Express会关闭调试输出某些库会跳过开发依赖。其次是npm ci --onlyproduction只装生产依赖镜像能小不少。FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction --registryhttps://registry.npmmirror.com COPY . . ENV NODE_ENVproduction EXPOSE 3000 USER node CMD [node, server.js]USER node这一行很多人会漏掉。Node官方镜像里默认有个node用户用它来运行应用比用root安全。如果应用被攻破攻击者拿到的只是普通用户权限不能直接改系统文件。这个细节在生产环境很重要。还有一个坑如果你用了nodemon或者ts-node这类开发工具它们属于devDependencies--onlyproduction不会安装。所以启动命令必须是node server.js不能是nodemon server.js。我见过有人本地开发用nodemonDockerfile里也写nodemon结果生产镜像里根本没这个命令容器起来就退出。2.3 MongoDB用官方镜像还是自己构建MongoDB直接用官方镜像就行没必要自己构建。但有几个配置必须做。第一是数据持久化容器删了数据不能丢需要挂载volume。第二是认证默认MongoDB没有密码暴露到公网就是灾难。第三是初始化脚本第一次启动时自动创建数据库和用户。mongo: image: mongo:6.0 restart: always environment: MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} MONGO_INITDB_DATABASE: myapp volumes: - mongo_data:/data/db - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro networks: - app-networkMONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD只在第一次启动时生效如果volume里已经有数据了这两个环境变量会被忽略。所以如果你改了密码要么删掉volume重新初始化要么进容器手动改。这个机制很多人不知道改了密码发现没生效排查半天。init-mongo.js是初始化脚本容器第一次启动时会自动执行。里面可以创建应用专用的数据库用户而不是直接用root账号连数据库db db.getSiblingDB(myapp); db.createUser({ user: appuser, pwd: apppassword, roles: [{ role: readWrite, db: myapp }] });用专用用户而不是root是最小权限原则的体现。应用只需要读写自己的数据库不需要管理员权限。3. Docker Compose编排服务依赖与网络配置3.1 服务启动顺序与健康检查Docker Compose默认不保证启动顺序depends_on只保证容器启动的先后不保证服务真正可用。Node后端启动时如果MongoDB还没准备好接受连接就会报错退出。这个问题在第一次部署时特别常见。解决方案是加健康检查。MongoDB官方镜像自带mongosh可以用它来检测mongo: healthcheck: test: echo db.runCommand(ping).ok | mongosh localhost:27017/test --quiet interval: 10s timeout: 5s retries: 5 start_period: 30s backend: depends_on: mongo: condition: service_healthystart_period是给MongoDB留的初始化时间第一次启动要建数据目录、初始化用户可能需要十几秒。retries: 5配合interval: 10s最多等50秒一般够用。Node后端这边如果连接失败就退出Docker的restart: always会自动重启容器。但更好的做法是在代码里做重试。比如用Mongoose连接时const connectWithRetry () { mongoose.connect(process.env.MONGO_URI, { serverSelectionTimeoutMS: 5000, }).catch(err { console.error(MongoDB连接失败5秒后重试, err.message); setTimeout(connectWithRetry, 5000); }); }; connectWithRetry();这样即使MongoDB晚几秒启动Node服务也能自己恢复不用依赖Docker重启。3.2 网络隔离与端口暴露策略Docker Compose默认会创建一个bridge网络所有服务在同一个网络里可以通过服务名互相访问。但默认情况下所有端口都会暴露到宿主机。这有两个问题一是MongoDB的27017端口暴露到公网非常危险二是端口冲突。正确的做法是只暴露必要的端口。前端Nginx需要暴露80或443给外部访问Node后端只需要在内部网络里被Nginx访问MongoDB只需要被Node访问。所以services: frontend: ports: - 80:80 backend: expose: - 3000 mongo: expose: - 27017ports是把容器端口映射到宿主机expose只是声明容器端口对内部网络可见不映射到宿主机。这样MongoDB和Node都只能通过Docker内部网络访问外部完全碰不到。如果你需要从宿主机直接连MongoDB调试可以临时用docker exec -it mongo mongosh进容器操作不需要暴露端口。3.3 环境变量管理与敏感信息处理数据库密码、API密钥这些敏感信息不能硬编码在docker-compose.yml里否则提交到代码仓库就泄露了。Docker Compose支持.env文件把敏感信息放在里面.env加入.gitignore。# .env MONGO_PASSWORDyour_strong_password_here JWT_SECRETyour_jwt_secret_here然后在docker-compose.yml里引用environment: MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD} JWT_SECRET: ${JWT_SECRET}Node后端连接MongoDB的URI也要用环境变量拼environment: MONGO_URI: mongodb://appuser:${MONGO_APP_PASSWORD}mongo:27017/myapp?authSourcemyapp注意authSourcemyapp因为应用用户是在myapp数据库里创建的认证也要在这个数据库里做。如果不加这个参数MongoDB会默认在admin数据库里认证然后报认证失败。这个坑我踩过排查了很久才发现是authSource的问题。4. 数据持久化与备份恢复的实操方案4.1 Volume vs Bind Mount的选择Docker有两种数据持久化方式volume和bind mount。Volume由Docker管理存在/var/lib/docker/volumes/下跨平台一致性能好。Bind mount直接把宿主机目录挂到容器里方便直接查看文件但依赖宿主机的目录结构。MongoDB的数据目录建议用volume因为Docker对volume做了优化而且不会因为宿主机目录权限问题导致MongoDB启动失败。我见过用bind mount挂载MongoDB数据目录结果因为宿主机目录属主是rootMongoDB容器里的mongodb用户没权限写启动直接报错。volumes: mongo_data: driver: local如果确实需要直接访问数据文件可以用docker cp从容器里拷出来或者用mongodump导出。4.2 定期备份的自动化脚本生产环境必须做定期备份。MongoDB官方提供了mongodump工具可以在容器里执行#!/bin/bash BACKUP_DIR/backup/mongo DATE$(date %Y%m%d_%H%M%S) docker exec mongo mongodump \ --username admin \ --password ${MONGO_PASSWORD} \ --authenticationDatabase admin \ --out /tmp/backup_${DATE} docker cp mongo:/tmp/backup_${DATE} ${BACKUP_DIR}/ docker exec mongo rm -rf /tmp/backup_${DATE} # 删除7天前的备份 find ${BACKUP_DIR} -type d -mtime 7 -exec rm -rf {} \;这个脚本可以加到crontab里每天执行。注意--authenticationDatabase admin因为用的是root账号认证在admin库。备份文件拷出来后要删掉容器里的临时文件否则容器磁盘会越来越大。恢复的时候用mongorestoredocker cp ${BACKUP_DIR}/backup_20240101_120000 mongo:/tmp/restore docker exec mongo mongorestore \ --username admin \ --password ${MONGO_PASSWORD} \ --authenticationDatabase admin \ --drop \ /tmp/restore--drop参数会先删除现有集合再恢复确保数据完全一致。如果不加恢复时会追加数据可能导致重复。4.3 数据迁移的注意事项从开发环境迁移到生产环境时数据库连接字符串会变。开发环境可能是mongodb://localhost:27017/myapp生产环境是mongodb://appuser:passwordmongo:27017/myapp?authSourcemyapp。这个变化通过环境变量控制代码里不要写死。另外MongoDB的版本要一致。开发用6.0生产也用6.0。大版本升级比如5.0到6.0可能有兼容性问题需要先测试。Docker镜像的tag要写具体版本不要用latest否则某天重新构建时拉到了新版本可能就跑不起来了。5. 部署上线的完整流程与常见故障排查5.1 从零到上线的操作步骤假设你有一台全新的Linux服务器装了Docker和Docker Compose部署流程如下把代码克隆到服务器进入项目根目录。创建.env文件填入数据库密码、JWT密钥等敏感信息。执行docker compose build构建前端和后端镜像。第一次构建会比较慢因为要下载基础镜像和npm依赖。执行docker compose up -d后台启动所有服务。执行docker compose ps检查所有容器是否都是Up状态。执行docker compose logs -f backend查看后端日志确认MongoDB连接成功。浏览器访问服务器IP确认前端页面正常加载API请求正常返回。如果第6步看到MongoServerError: Authentication failed检查.env里的密码和docker-compose.yml里的MONGO_URI是否一致以及authSource参数是否正确。5.2 容器启动失败的排查链路容器起不来第一步是看日志docker compose logs service_name。如果日志里没有有用信息看容器状态docker compose ps -a看退出码。退出码137通常是内存不足被OOM Killer杀了退出码1是应用报错。如果容器一直在重启用docker compose up service_name前台启动这样能看到完整的输出。或者用docker run -it image sh进镜像里手动执行启动命令看报什么错。网络问题排查docker compose exec backend ping mongo如果能ping通说明网络没问题。如果ping不通检查两个服务是否在同一个网络里。docker network ls可以看到Compose创建的网络docker network inspect network_name可以看到哪些容器接入了。端口问题排查docker compose port frontend 80可以看到宿主机上实际映射的端口。如果显示的不是80说明80被占用了Compose自动换了端口。用netstat -tlnp | grep 80看谁占了80端口。5.3 性能调优的几个实用参数Node后端在生产环境需要调几个参数。NODE_OPTIONS--max-old-space-size512限制Node堆内存防止内存泄漏把服务器拖垮。UV_THREADPOOL_SIZE4根据CPU核心数调整线程池大小默认是4如果服务器核心多可以调大。MongoDB这边--wiredTigerCacheSizeGB控制WiredTiger引擎的缓存大小默认是物理内存的一半。如果服务器还跑了其他服务可以调小一点比如--wiredTigerCacheSizeGB 1。Nginx的worker_processes设为autoworker_connections设为1024对于一般Web应用够用了。如果并发高可以开gzip压缩减少传输体积gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1000;5.4 我踩过的三个坑第一个坑是时区问题。容器默认用UTC时间Node里new Date()出来的时间比北京时间少8小时。解决方案是在Dockerfile里设ENV TZAsia/Shanghai或者挂载宿主机的/etc/localtime。我建议用环境变量更干净。第二个坑是文件上传的路径。Node后端如果把上传的文件存在容器里的/app/uploads容器重建后文件就丢了。正确做法是把上传目录挂载到volume或者宿主机目录backend: volumes: - uploads_data:/app/uploads第三个坑是前端API地址写死。Vue项目里如果axios.defaults.baseURL写的是http://localhost:3000部署到服务器后浏览器会去访问用户自己的localhost而不是服务器。正确做法是用相对路径/api让Nginx代理转发。或者用环境变量在构建时注入但相对路径更简单可靠。6. 日常运维日志、更新与监控6.1 日志收集与查看技巧Docker默认把容器日志存在/var/lib/docker/containers/下用docker compose logs查看。但默认的json-file驱动不会自动轮转日志会一直涨。需要在docker-compose.yml里配置services: backend: logging: driver: json-file options: max-size: 10m max-file: 3这样每个日志文件最大10MB最多保留3个总共不超过30MB。对于大多数应用够用了。查看日志时docker compose logs -f --tail100 backend可以只看最后100行并持续跟踪。docker compose logs --since 30m backend看最近30分钟的日志。这些参数在排查问题时很实用。6.2 滚动更新与回滚更新应用时不要直接docker compose down再up这样服务会中断。正确的做法是重新构建镜像然后docker compose up -dCompose会检测到镜像变化重建对应的容器。但默认是停止旧容器再启动新容器还是有短暂中断。如果要零中断需要配合Nginx的upstream和健康检查或者用Docker Swarm的滚动更新。对于一般项目短暂中断可以接受的话docker compose up -d就够了。回滚的话把代码切回上一个版本重新docker compose build docker compose up -d。所以每次上线前要打tag方便回滚。6.3 资源监控的简易方案docker stats可以实时看容器的CPU、内存、网络IO。但这个命令是交互式的不适合长期监控。可以用docker stats --no-stream输出一次快照配合cron定期记录。更完整的方案是部署Prometheus Grafana但这对于小项目有点重。一个折中方案是用cAdvisor它专门收集容器指标自带Web界面cadvisor: image: gcr.io/cadvisor/cadvisor:latest volumes: - /:/rootfs:ro - /var/run:/var/run:ro - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro ports: - 8080:8080访问http://服务器IP:8080就能看到所有容器的资源使用情况。注意这个端口不要暴露到公网只在内网访问。我个人在实际运维中的体会是监控不用一开始就搞得很复杂。先用docker stats和日志等真正遇到性能问题了再上专业工具。过早引入复杂的监控系统维护成本可能比收益还高。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑