服务器多项目部署规范

一个服务器部署多个项目的完整规范,包括端口规划、Docker 容器配置、Nginx 反向代理配置等。

适用场景:腾讯云 Ubuntu 服务器,使用 Docker 和 Nginx 部署多个前后端项目

涉及技术:Docker、Docker Compose、Nginx、端口管理、反向代理


📚 目录


1. 端口规划规范

1.1 端口分配原则

端口分类

  1. 系统服务端口(1-1023):系统保留,禁止使用
  2. 反向代理端口(80, 443):Nginx 监听端口,对外暴露
  3. 前端服务端口(3000-3999):前端应用容器内部端口
  4. 后端服务端口(4000-4999):后端 API 服务容器内部端口
  5. 数据库端口(5000-5999):数据库服务端口(如 MySQL 3306, Redis 6379)
  6. 其他服务端口(6000-9999):其他服务(如 WebSocket、SSH 隧道等)

1.2 端口分配表

端口范围用途说明
80Nginx HTTP对外暴露,所有 HTTP 请求入口
443Nginx 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: bridge

2.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:latest

3. 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 nginx

3.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: bridge

4.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-stopped

4.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-stopped

5. 腾讯云 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 status

5.2 腾讯云安全组配置

在腾讯云控制台配置

  1. 登录腾讯云控制台
  2. 进入「云服务器」「安全组」
  3. 选择对应的安全组规则
  4. 添加入站规则:
    • HTTP:端口 80,协议 TCP,来源 0.0.0.0/0
    • HTTPS:端口 443,协议 TCP,来源 0.0.0.0/0
    • SSH:端口 22,协议 TCP,来源 你的IP(建议限制来源)

注意

  • 不要开放容器内部端口(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.log

5.4 自动启动配置

# Docker 服务自动启动(默认已启用)
sudo systemctl enable docker
 
# Nginx 服务自动启动
sudo systemctl enable nginx
 
# 使用 Docker Compose 时,容器 restart 策略已配置
# restart: unless-stopped 会在系统重启后自动启动容器

6. 最佳实践

6.1 端口管理

  1. 统一端口规划:建立端口分配表,记录每个项目的端口使用情况
  2. 避免端口冲突:部署前检查端口占用情况
  3. 预留端口范围:为未来项目预留端口范围
  4. 文档记录:在项目 README 中记录端口配置

6.2 Docker 容器管理

  1. 使用 Docker Compose:统一管理多容器应用
  2. 网络隔离:不同项目使用不同的 Docker 网络
  3. 数据持久化:使用 Docker volumes 存储数据库数据
  4. 资源限制:为容器设置 CPU 和内存限制
# 资源限制示例
services:
  backend:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

6.3 Nginx 配置优化

  1. 使用域名区分:优先使用域名方式,用户体验最好
  2. 配置缓存:为静态资源配置缓存
  3. 启用 HTTPS:使用 Let’s Encrypt 免费证书
  4. 日志管理:定期清理日志文件,避免磁盘占满
# 静态资源缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

6.4 安全建议

  1. 最小权限原则:容器内使用非 root 用户运行
  2. 环境变量管理:敏感信息使用环境变量,不要硬编码
  3. 定期更新:定期更新 Docker 镜像和系统包
  4. 备份策略:定期备份数据库和重要数据

6.5 监控与日志

  1. 容器日志:使用 docker logs 查看容器日志
  2. Nginx 日志:定期检查 Nginx 访问日志和错误日志
  3. 系统监控:使用 htopdocker stats 监控资源使用
  4. 告警机制:配置异常告警(可选)

6.6 部署流程

  1. 构建镜像:在本地或 CI/CD 中构建 Docker 镜像
  2. 推送镜像:推送到 Docker Hub 或私有仓库
  3. 服务器拉取:在服务器上拉取最新镜像
  4. 更新容器:使用 docker-compose up -d 更新容器
  5. 验证部署:检查服务是否正常运行
  6. 回滚准备:保留旧版本镜像,便于快速回滚
# 部署脚本示例
#!/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: 后端项目预留

🔗 相关链接

前置知识

进阶学习


最后更新:2026-01-28
维护规范:每次新增项目时,更新端口分配表和 Nginx 配置


部署 Docker Nginx 反向代理 多项目部署 端口管理 腾讯云 Ubuntu