服务器多项目部署规范
一个服务器部署多个项目的完整规范,包括端口规划、Docker 容器配置、Nginx 反向代理配置等。
适用场景:腾讯云 Ubuntu 服务器,使用 Docker 和 Nginx 部署多个前后端项目
涉及技术:Docker、Docker Compose、Nginx、端口管理、反向代理
📚 目录
1. 端口规划规范
1.1 端口分配原则
端口分类:
- 系统服务端口(1-1023):系统保留,禁止使用
- 反向代理端口(80, 443):Nginx 监听端口,对外暴露
- 前端服务端口(3000-3999):前端应用容器内部端口
- 后端服务端口(4000-4999):后端 API 服务容器内部端口
- 数据库端口(5000-5999):数据库服务端口(如 MySQL 3306, Redis 6379)
- 其他服务端口(6000-9999):其他服务(如 WebSocket、SSH 隧道等)
1.2 端口分配表
| 端口范围 | 用途 | 说明 |
|---|---|---|
| 80 | Nginx HTTP | 对外暴露,所有 HTTP 请求入口 |
| 443 | Nginx HTTPS | 对外暴露,所有 HTTPS 请求入口 |
| 3000-3099 | 前端项目 1-10 | 前端应用容器端口 |
| 3100-3199 | 前端项目 11-20 | 前端应用容器端口(预留) |
| 4000-4099 | 后端项目 1-10 | 后端 API 服务容器端口 |
| 4100-4199 | 后端项目 11-20 | 后端 API 服务容器端口(预留) |
| 5000-5099 | 数据库服务 | MySQL、PostgreSQL、MongoDB 等 |
| 6000-6999 | 其他服务 | WebSocket、消息队列等 |
| 7000-7999 | 开发调试 | 临时开发端口 |
1.3 项目端口分配示例
项目 A(全栈项目):
- 前端容器端口:
3001 - 后端容器端口:
4001 - 数据库端口:
3306(MySQL)
项目 B(全栈项目):
- 前端容器端口:
3002 - 后端容器端口:
4002 - 数据库端口:
3307(MySQL)
项目 C(纯前端项目):
- 前端容器端口:
3003 - 无后端服务
项目 D(纯后端项目):
- 后端容器端口:
4003 - 无前端服务
2. Docker 容器端口映射
2.1 端口映射规则
格式:-p 宿主机端口:容器内部端口
原则:
- 宿主机端口 = 容器内部端口(保持一致,便于管理)
- 容器内部端口使用规划好的端口范围
- 避免端口冲突
2.2 Docker Compose 配置示例
# docker-compose.yml
version: '3.8'
services:
# 项目 A - 前端
project-a-frontend:
image: project-a-frontend:latest
container_name: project-a-frontend
ports:
- "3001:3001" # 宿主机:容器
environment:
- NODE_ENV=production
- API_URL=http://project-a-backend:4001
networks:
- project-a-network
restart: unless-stopped
# 项目 A - 后端
project-a-backend:
image: project-a-backend:latest
container_name: project-a-backend
ports:
- "4001:4001" # 宿主机:容器
environment:
- NODE_ENV=production
- DB_HOST=project-a-db
- DB_PORT=3306
networks:
- project-a-network
depends_on:
- project-a-db
restart: unless-stopped
# 项目 A - 数据库
project-a-db:
image: mysql:8.0
container_name: project-a-db
ports:
- "3306:3306" # 可选:如果需要外部访问
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=project_a
volumes:
- project-a-db-data:/var/lib/mysql
networks:
- project-a-network
restart: unless-stopped
# 项目 B - 前端
project-b-frontend:
image: project-b-frontend:latest
container_name: project-b-frontend
ports:
- "3002:3002"
environment:
- NODE_ENV=production
- API_URL=http://project-b-backend:4002
networks:
- project-b-network
restart: unless-stopped
# 项目 B - 后端
project-b-backend:
image: project-b-backend:latest
container_name: project-b-backend
ports:
- "4002:4002"
environment:
- NODE_ENV=production
- DB_HOST=project-b-db
- DB_PORT=3306
networks:
- project-b-network
depends_on:
- project-b-db
restart: unless-stopped
# 项目 B - 数据库
project-b-db:
image: mysql:8.0
container_name: project-b-db
ports:
- "3307:3306" # 宿主机使用 3307,容器内仍是 3306
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=project_b
volumes:
- project-b-db-data:/var/lib/mysql
networks:
- project-b-network
restart: unless-stopped
volumes:
project-a-db-data:
project-b-db-data:
networks:
project-a-network:
driver: bridge
project-b-network:
driver: bridge2.3 独立 Docker 运行示例
# 项目 A 前端
docker run -d \
--name project-a-frontend \
-p 3001:3001 \
-e NODE_ENV=production \
-e API_URL=http://localhost:4001 \
project-a-frontend:latest
# 项目 A 后端
docker run -d \
--name project-a-backend \
-p 4001:4001 \
-e NODE_ENV=production \
-e DB_HOST=project-a-db \
project-a-backend:latest
# 项目 B 前端
docker run -d \
--name project-b-frontend \
-p 3002:3002 \
-e NODE_ENV=production \
-e API_URL=http://localhost:4002 \
project-b-frontend:latest
# 项目 B 后端
docker run -d \
--name project-b-backend \
-p 4002:4002 \
-e NODE_ENV=production \
-e DB_HOST=project-b-db \
project-b-backend:latest3. Nginx 反向代理配置
3.1 Nginx 主配置文件
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
# 基础配置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 包含站点配置
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}3.2 方式一:通过域名区分(推荐)
适用场景:有多个域名或子域名
# /etc/nginx/sites-available/project-a
server {
listen 80;
listen [::]:80;
server_name project-a.example.com www.project-a.example.com;
# 前端代理
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
# 后端 API 代理
location /api {
proxy_pass http://localhost:4001;
proxy_http_version 1.1;
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;
}
}
# /etc/nginx/sites-available/project-b
server {
listen 80;
listen [::]:80;
server_name project-b.example.com www.project-b.example.com;
location / {
proxy_pass http://localhost:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
location /api {
proxy_pass http://localhost:4002;
proxy_http_version 1.1;
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;
}
}启用配置:
# 创建软链接启用配置
sudo ln -s /etc/nginx/sites-available/project-a /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/project-b /etc/nginx/sites-enabled/
# 验证配置
sudo nginx -t
# 重载配置
sudo systemctl reload nginx3.3 方式二:通过路径区分(无域名时使用)
适用场景:只有一个 IP 地址,没有域名
# /etc/nginx/sites-available/multi-projects
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# 项目 A - 前端
location /project-a {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
# 重写路径(如果前端需要)
rewrite ^/project-a/(.*)$ /$1 break;
}
# 项目 A - 后端 API
location /project-a/api {
proxy_pass http://localhost:4001;
proxy_http_version 1.1;
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;
# 重写路径,去掉 /project-a 前缀
rewrite ^/project-a/api/(.*)$ /api/$1 break;
}
# 项目 B - 前端
location /project-b {
proxy_pass http://localhost:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
rewrite ^/project-b/(.*)$ /$1 break;
}
# 项目 B - 后端 API
location /project-b/api {
proxy_pass http://localhost:4002;
proxy_http_version 1.1;
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;
rewrite ^/project-b/api/(.*)$ /api/$1 break;
}
# 默认根路径
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ =404;
}
}注意:使用路径区分时,前端项目需要配置 base 路径(见下方前端配置)
3.4 方式三:通过端口区分(不推荐)
适用场景:临时测试,不推荐生产环境
# 项目 A - 8080 端口
server {
listen 8080;
server_name _;
location / {
proxy_pass http://localhost:3001;
# ... 其他配置
}
location /api {
proxy_pass http://localhost:4001;
# ... 其他配置
}
}
# 项目 B - 8081 端口
server {
listen 8081;
server_name _;
location / {
proxy_pass http://localhost:3002;
# ... 其他配置
}
location /api {
proxy_pass http://localhost:4002;
# ... 其他配置
}
}缺点:
- 需要开放多个端口
- 防火墙配置复杂
- 用户体验差(需要记住端口号)
4. 项目部署示例
4.1 项目 A:全栈项目(React + Node.js)
4.1.1 前端 Dockerfile
# project-a-frontend/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
RUN npm ci
# 复制源代码
COPY . .
# 构建
RUN npm run build
# 生产环境镜像
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 Nginx 配置(如果需要)
# COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 3001
CMD ["nginx", "-g", "daemon off;"]4.1.2 后端 Dockerfile
# project-a-backend/Dockerfile
FROM node:18-alpine
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
RUN npm ci --only=production
# 复制源代码
COPY . .
EXPOSE 4001
CMD ["node", "server.js"]4.1.3 Docker Compose 配置
# project-a/docker-compose.yml
version: '3.8'
services:
frontend:
build: ./frontend
container_name: project-a-frontend
ports:
- "3001:80" # Nginx 默认 80,映射到宿主机 3001
environment:
- NODE_ENV=production
networks:
- project-a-network
restart: unless-stopped
backend:
build: ./backend
container_name: project-a-backend
ports:
- "4001:4001"
environment:
- NODE_ENV=production
- DB_HOST=db
- DB_PORT=3306
- DB_NAME=project_a
- DB_USER=root
- DB_PASSWORD=root_password
networks:
- project-a-network
depends_on:
- db
restart: unless-stopped
db:
image: mysql:8.0
container_name: project-a-db
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=project_a
volumes:
- project-a-db-data:/var/lib/mysql
networks:
- project-a-network
restart: unless-stopped
volumes:
project-a-db-data:
networks:
project-a-network:
driver: bridge4.1.4 前端配置(路径部署时)
Vite 项目 (vite.config.js):
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: '/project-a/', // 如果使用路径部署
build: {
outDir: 'dist',
}
})Create React App (package.json):
{
"homepage": "/project-a"
}React Router (App.jsx):
import { BrowserRouter } from 'react-router-dom'
function App() {
return (
<BrowserRouter basename="/project-a">
{/* 路由配置 */}
</BrowserRouter>
)
}4.2 项目 B:纯前端项目(Vue 3)
4.2.1 Dockerfile
# project-b/Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 3002
CMD ["nginx", "-g", "daemon off;"]4.2.2 Docker Compose 配置
# project-b/docker-compose.yml
version: '3.8'
services:
frontend:
build: .
container_name: project-b-frontend
ports:
- "3002:80"
restart: unless-stopped4.3 项目 C:纯后端项目(Express API)
4.3.1 Dockerfile
# project-c/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 4003
CMD ["node", "index.js"]4.3.2 Docker Compose 配置
# project-c/docker-compose.yml
version: '3.8'
services:
backend:
build: .
container_name: project-c-backend
ports:
- "4003:4003"
environment:
- NODE_ENV=production
restart: unless-stopped5. 腾讯云 Ubuntu 特殊配置
5.1 防火墙配置(UFW)
# 安装 UFW(如果未安装)
sudo apt update
sudo apt install ufw -y
# 允许 SSH(重要:先允许 SSH,避免被锁在外面)
sudo ufw allow 22/tcp
# 允许 HTTP 和 HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 如果需要直接访问容器端口(不推荐,应通过 Nginx)
# sudo ufw allow 3001/tcp
# sudo ufw allow 4001/tcp
# 启用防火墙
sudo ufw enable
# 查看防火墙状态
sudo ufw status5.2 腾讯云安全组配置
在腾讯云控制台配置:
- 登录腾讯云控制台
- 进入「云服务器」→「安全组」
- 选择对应的安全组规则
- 添加入站规则:
- HTTP:端口
80,协议TCP,来源0.0.0.0/0 - HTTPS:端口
443,协议TCP,来源0.0.0.0/0 - SSH:端口
22,协议TCP,来源你的IP(建议限制来源)
- HTTP:端口
注意:
- 不要开放容器内部端口(3001, 4001 等)到公网
- 所有外部请求应通过 Nginx(80/443 端口)进入
- 容器端口只在服务器内部访问
5.3 系统资源监控
# 查看端口占用
sudo netstat -tlnp | grep LISTEN
# 或使用
sudo ss -tlnp
# 查看 Docker 容器状态
docker ps
# 查看容器资源使用
docker stats
# 查看 Nginx 状态
sudo systemctl status nginx
# 查看 Nginx 日志
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log5.4 自动启动配置
# Docker 服务自动启动(默认已启用)
sudo systemctl enable docker
# Nginx 服务自动启动
sudo systemctl enable nginx
# 使用 Docker Compose 时,容器 restart 策略已配置
# restart: unless-stopped 会在系统重启后自动启动容器6. 最佳实践
6.1 端口管理
- 统一端口规划:建立端口分配表,记录每个项目的端口使用情况
- 避免端口冲突:部署前检查端口占用情况
- 预留端口范围:为未来项目预留端口范围
- 文档记录:在项目 README 中记录端口配置
6.2 Docker 容器管理
- 使用 Docker Compose:统一管理多容器应用
- 网络隔离:不同项目使用不同的 Docker 网络
- 数据持久化:使用 Docker volumes 存储数据库数据
- 资源限制:为容器设置 CPU 和内存限制
# 资源限制示例
services:
backend:
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M6.3 Nginx 配置优化
- 使用域名区分:优先使用域名方式,用户体验最好
- 配置缓存:为静态资源配置缓存
- 启用 HTTPS:使用 Let’s Encrypt 免费证书
- 日志管理:定期清理日志文件,避免磁盘占满
# 静态资源缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}6.4 安全建议
- 最小权限原则:容器内使用非 root 用户运行
- 环境变量管理:敏感信息使用环境变量,不要硬编码
- 定期更新:定期更新 Docker 镜像和系统包
- 备份策略:定期备份数据库和重要数据
6.5 监控与日志
- 容器日志:使用
docker logs查看容器日志 - Nginx 日志:定期检查 Nginx 访问日志和错误日志
- 系统监控:使用
htop、docker stats监控资源使用 - 告警机制:配置异常告警(可选)
6.6 部署流程
- 构建镜像:在本地或 CI/CD 中构建 Docker 镜像
- 推送镜像:推送到 Docker Hub 或私有仓库
- 服务器拉取:在服务器上拉取最新镜像
- 更新容器:使用
docker-compose up -d更新容器 - 验证部署:检查服务是否正常运行
- 回滚准备:保留旧版本镜像,便于快速回滚
# 部署脚本示例
#!/bin/bash
# 拉取最新镜像
docker-compose pull
# 停止旧容器
docker-compose down
# 启动新容器
docker-compose up -d
# 清理旧镜像
docker image prune -f
# 验证服务
sleep 5
curl -f http://localhost:3001 || exit 1📋 端口分配表模板
创建 ports-allocation.md 文件记录端口使用情况:
# 服务器端口分配表
## 系统服务
- 22: SSH
- 80: Nginx HTTP
- 443: Nginx HTTPS
## 项目 A
- 3001: 前端容器
- 4001: 后端容器
- 3306: MySQL 数据库
## 项目 B
- 3002: 前端容器
- 4002: 后端容器
- 3307: MySQL 数据库
## 项目 C
- 3003: 前端容器
## 项目 D
- 4003: 后端容器
## 预留端口
- 3004-3099: 前端项目预留
- 4004-4099: 后端项目预留🔗 相关链接
前置知识
- Docker 核心概念 — Docker 基础
- Nginx 多项目配置 — Nginx 配置
- Docker Compose — 多服务编排
进阶学习
- 腾讯云部署 — 云平台部署实践
- React 子路径部署 — 前端子路径部署
最后更新:2026-01-28
维护规范:每次新增项目时,更新端口分配表和 Nginx 配置