Docker + Nginx Proxy Manager 在香港VPS管理多站点:一键HTTPS与容器编排实战

Docker + Nginx Proxy Manager 在香港VPS管理多站点:一键HTTPS与容器编排实战

在单台香港 VPS 上同时运行多个网站,传统做法是手动维护大量 Nginx 配置文件,每次添加站点、申请证书都要重复操作。Nginx Proxy Manager(NPM) 提供了一个可视化 Web 界面,结合 Docker 容器化,让多站点管理变得像填表格一样简单,同时实现完整的容器级隔离。


一、架构设计

<code">
外部请求(80/443)
        │
  ┌─────▼──────────────────────────────────────┐
  │       Nginx Proxy Manager(反向代理层)       │
  │  Web UI 管理 · 自动 Let's Encrypt 证书       │
  └──┬────────────────┬────────────────┬────────┘
     │                │                │
┌────▼────┐    ┌──────▼──────┐  ┌──────▼──────┐
│ WordPress│    │  Ghost Blog  │  │  Nextcloud  │
│  :8001   │    │   :8002      │  │   :8003     │
└──────────┘    └─────────────┘  └─────────────┘
  Docker 网络隔离,各容器独立运行

二、安装 Docker 与 Docker Compose

<code"># 一键安装 Docker
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

# 验证
docker --version
docker compose version

三、部署 Nginx Proxy Manager

<code">mkdir -p /opt/npm && cd /opt/npm

cat > docker-compose.yml << 'EOF'
version: '3.8'

networks:
  proxy-net:
    driver: bridge
    name: proxy-net          # 全局网络,所有站点容器加入此网络

services:
  nginx-proxy-manager:
    image: jc21/nginx-proxy-manager:latest
    container_name: npm
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"              # NPM Web 管理界面端口
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - proxy-net

  # NPM 内置数据库
  db:
    image: jc21/mariadb-aria:latest
    container_name: npm-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: npm_root_pass
      MYSQL_DATABASE: npm
      MYSQL_USER: npm
      MYSQL_PASSWORD: npm_pass
    volumes:
      - ./mysql:/var/lib/mysql
    networks:
      - proxy-net
EOF

docker compose up -d

访问 http://你的服务器IP:81,默认账号:admin@example.com / changeme,首次登录后立即修改密码。

四、部署多个站点容器

站点一:WordPress

<code">mkdir -p /opt/sites/wordpress && cd /opt/sites/wordpress

cat > docker-compose.yml << 'EOF'
version: '3.8'

networks:
  proxy-net:
    external: true            # 接入 NPM 共享网络

services:
  wordpress:
    image: wordpress:php8.1-fpm
    container_name: wp-site1
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: wp-mysql
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: wp_pass
      WORDPRESS_TABLE_PREFIX: wp_
    volumes:
      - ./wp-content:/var/www/html/wp-content
    networks:
      - proxy-net
    expose:
      - "9000"               # 只在内网暴露,不对外开放

  wp-mysql:
    image: mysql:8.0
    container_name: wp-mysql
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: wp_pass
      MYSQL_ROOT_PASSWORD: root_pass
    volumes:
      - ./mysql-data:/var/lib/mysql
    networks:
      - proxy-net
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M       # 资源限制,防止单站点耗尽服务器资源
EOF

docker compose up -d

站点二:Ghost 博客

<code">mkdir -p /opt/sites/ghost && cd /opt/sites/ghost

cat > docker-compose.yml << 'EOF'
version: '3.8'

networks:
  proxy-net:
    external: true

services:
  ghost:
    image: ghost:5-alpine
    container_name: ghost-blog
    restart: unless-stopped
    environment:
      url: https://blog.yourdomain.com
      database__client: mysql
      database__connection__host: ghost-mysql
      database__connection__database: ghost
      database__connection__user: ghost_user
      database__connection__password: ghost_pass
      mail__transport: SMTP
      mail__options__host: smtp.yourdomain.com
    volumes:
      - ./ghost-content:/var/lib/ghost/content
    networks:
      - proxy-net
    expose:
      - "2368"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M

  ghost-mysql:
    image: mysql:8.0
    container_name: ghost-mysql
    environment:
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost_user
      MYSQL_PASSWORD: ghost_pass
      MYSQL_ROOT_PASSWORD: root_pass
    volumes:
      - ./mysql-data:/var/lib/mysql
    networks:
      - proxy-net
EOF

docker compose up -d

五、在 Nginx Proxy Manager 中配置反向代理

进入 NPM Web 界面(http://IP:81),按以下步骤添加站点:

  1. 点击 Proxy Hosts → Add Proxy Host
  2. 填写:
    • Domain Names:site1.yourdomain.com
    • Forward Hostname / IP:wp-site1(容器名,NPM 通过 Docker 网络直接解析)
    • Forward Port:80
    • 勾选 Block Common Exploits(基础安全防护)
  3. 切换到 SSL 标签
    • 选择 Request a new SSL Certificate
    • 勾选 Force SSLHTTP/2 Support
    • 填写邮箱,点击 Save

NPM 会自动向 Let’s Encrypt 申请证书并配置 HTTPS,全程约 30 秒,无需命令行操作。

六、容器资源管理与监控

<code"># 查看所有站点容器资源占用
docker stats --no-stream

# 输出示例:
# CONTAINER         CPU %   MEM USAGE / LIMIT     NET I/O
# npm               0.2%    45MiB / 8GiB          ...
# wp-site1          0.8%    128MiB / 512MiB       ...
# ghost-blog        0.3%    89MiB / 256MiB        ...
# wp-mysql          0.5%    210MiB / 512MiB       ...
<code"># 查看某个容器的实时日志
docker logs -f wp-site1 --tail 50

# 进入容器调试
docker exec -it wp-site1 bash

# 重启单个站点(不影响其他站点)
docker restart ghost-blog

七、统一备份脚本(所有站点数据卷)

<code">cat > /usr/local/bin/backup-docker-sites.sh << 'SCRIPT' #!/bin/bash DATE=$(date +%Y%m%d) BACKUP_DIR="/backup/docker-sites/$DATE" mkdir -p $BACKUP_DIR # 备份各站点数据卷 for site in wordpress ghost; do docker run --rm \ -v /opt/sites/$site:/source:ro \ -v $BACKUP_DIR:/backup \ alpine \ tar -czf /backup/${site}-${DATE}.tar.gz -C /source . echo "已备份: $site" done # 清理 7 天前备份 find /backup/docker-sites -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + SCRIPT chmod +x /usr/local/bin/backup-docker-sites.sh echo "0 2 * * * /usr/local/bin/backup-docker-sites.sh" >> /etc/crontab

八、总结

Docker + Nginx Proxy Manager 的组合将多站点管理从繁琐的命令行操作转变为直观的 Web 界面操作,HTTPS 证书申请、反向代理配置一键完成,容器隔离保证各站点互不影响。这套方案特别适合在单台香港 VPS 上同时运营 3~10 个网站的场景,充分利用服务器资源的同时保持良好的运维可控性。

Telegram