欢迎光临
我们一直在努力

Docker + Docker Compose 实战:在VPS上用容器化方案部署高可用Web应用的完整指南

随着 DockerDocker Compose 技术的日益成熟,越来越多的站长和开发者选择在 VPS 上使用容器化方案来部署 Web 应用。相比于传统的 LNMP 环境搭建方式,容器化部署具有环境隔离、快速迁移、版本控制、水平扩展等显著优势。本文将基于实战经验,从零开始详细讲解如何在 VPS 上使用 Docker + Docker Compose 构建一套高可用的 Web 应用栈,涵盖 Nginx 反向代理、PHP-FPM、MySQL、Redis、WordPress 等常见服务的容器化部署,并配合 Let’s Encrypt 自动续签 HTTPS 证书,真正做到开箱即用、维护无忧。

无论你是刚入门的小白还是有一定经验的老手,这篇文章都会提供大量可运行的代码示例和最佳实践,帮助你彻底掌握 VPS 容器化部署的精髓。

Docker容器化部署

一、为什么要在 VPS 上使用 Docker 部署 Web 应用

在传统的 VPS 建站方案中,我们通常直接在宿主机上安装 Nginx、MySQL、PHP 等组件。这种方式虽然直观,但在实际运维中会面临几个难以回避的问题:

对比维度 传统 LNMP 部署 Docker 容器化部署
环境一致性 依赖系统包管理器版本,迁移需重新配置 镜像构建一次,到处运行
依赖冲突 多个项目可能需要不同 PHP/MySQL 版本 每个容器独立版本,互不干扰
备份与恢复 散落在系统各目录,备份脚本需精确指定路径 通过 docker-compose.yml + 数据卷即可完整还原
资源隔离 一个服务崩了可能拖垮整个系统 容器级隔离,崩溃不影响其他服务
扩展能力 单机部署,扩展需要重新配置 天然支持多副本和负载均衡

对于一台 1核1G 甚至 512MB 内存的小鸡来说,Docker 的资源开销完全可以接受——Docker 引擎本身大约占用 50-100MB 内存,相比它带来的运维便利性,这点开销是值得的。

二、基础环境准备:安装 Docker 和 Docker Compose

在开始之前,我们先在 VPS 上安装 Docker 环境。以下操作基于 Ubuntu 22.04(Debian 系操作类似)。

2.1 一键安装 Docker

# 卸载旧版本
sudo apt remove docker docker-engine docker.io containerd runc

# 安装依赖并添加 Docker 官方源
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker 引擎
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 将当前用户加入 docker 组(避免每次 sudo)
sudo usermod -aG docker $USER
newgrp docker

# 验证安装
docker --version && docker compose version

2.2 系统调优:让 Docker 运行更稳定

安装完成后,对 Docker 的存储驱动和网络做一些基础调优,尤其对低配 VPS 至关重要:

# 配置 Docker 使用 overlay2 存储驱动(默认已是)
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
}
EOF

# 重启 Docker
sudo systemctl restart docker

# 确认配置生效
docker info | grep -i "storage driver"

日志限制非常重要——默认情况下 Docker 不限制容器日志文件大小,运行几周后日志文件可能吃掉你十几 GB 磁盘空间。上面的 max-size: 10m, max-file: 3 将每个容器的日志限制在 30MB 以内。

三、构建核心 Web 服务栈:Nginx + PHP-FPM + MySQL

这一节我们构建一个标准的 Web 服务栈。采用分层设计,让各个服务通过 Docker 内部网络通信。

3.1 项目目录结构

首先建立清晰的项目目录结构,这将极大方便后续的维护和备份:

mkdir -p ~/docker-web/{nginx/{conf.d,ssl,logs},php,www,db}
cd ~/docker-web
tree -L 2

预期的目录结构如下:

docker-web/
├── docker-compose.yml
├── nginx/
│   ├── conf.d/       # Nginx 站点配置文件
│   ├── ssl/          # SSL 证书目录
│   └── logs/         # Nginx 日志
├── php/
│   └── Dockerfile    # 自定义 PHP 镜像
├── www/              # 网站源码根目录
└── db/               # MySQL 数据持久化目录

3.2 创建 docker-compose.yml

version: '3.8'

services:
  nginx:
    image: nginx:1.25-alpine
    container_name: web-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
      - ./nginx/logs:/var/log/nginx
      - ./www:/var/www/html
    networks:
      - webnet
    depends_on:
      - php
    restart: unless-stopped

  php:
    build: ./php
    container_name: web-php
    volumes:
      - ./www:/var/www/html
    networks:
      - webnet
    environment:
      - PHP_MEMORY_LIMIT=256M
      - PHP_MAX_EXECUTION_TIME=300
      - PHP_POST_MAX_SIZE=64M
      - PHP_UPLOAD_MAX_FILESIZE=64M
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    container_name: web-mysql
    volumes:
      - ./db:/var/lib/mysql
    networks:
      - webnet
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-wordpress}
      MYSQL_USER: ${MYSQL_USER:-wpuser}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    command: --innodb_buffer_pool_size=256M --max_connections=100
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: web-redis
    volumes:
      - redis-data:/data
    networks:
      - webnet
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 128mb --maxmemory-policy allkeys-lru
    restart: unless-stopped

networks:
  webnet:
    driver: bridge

volumes:
  redis-data:

3.3 自定义 PHP Dockerfile

创建一个包含常用 PHP 扩展的自定义镜像,这对运行 WordPress 和大多数 PHP 应用是必需的:

cat > ~/docker-web/php/Dockerfile <<'EOF'
FROM php:8.2-fpm-alpine

# 安装系统依赖
RUN apk add --no-cache \
    freetype-dev \
    libjpeg-turbo-dev \
    libpng-dev \
    libzip-dev \
    zip \
    unzip \
    curl \
    git

# 安装 PHP 扩展
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
    gd \
    pdo_mysql \
    mysqli \
    opcache \
    zip \
    bcmath \
    exif

# 安装 Redis 扩展
RUN pecl install redis && docker-php-ext-enable redis

# 配置 OPcache
RUN { \
    echo 'opcache.memory_consumption=128'; \
    echo 'opcache.interned_strings_buffer=8'; \
    echo 'opcache.max_accelerated_files=10000'; \
    echo 'opcache.revalidate_freq=2'; \
    echo 'opcache.fast_shutdown=1'; \
    echo 'opcache.enable_cli=1'; \
} > /usr/local/etc/php/conf.d/opcache.ini

# 安装 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html
EOF

四、创建 .env 环境变量文件

敏感信息(数据库密码、Redis 密码等)不应该硬编码在 docker-compose.yml 中。在同一目录下创建 .env 文件:

cat > ~/docker-web/.env <<'EOF'
MYSQL_ROOT_PASSWORD=YourStrongRootPassword123!
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=YourStrongUserPassword456!
REDIS_PASSWORD=YourRedisPassword789!
EOF

# 设置 .env 文件权限,防止被其他用户读取
chmod 600 ~/docker-web/.env

五、配置 Nginx 反向代理与 SSL

5.1 Nginx 站点配置文件

创建一个通用的 Nginx 站点配置,支持 PHP 解析和 HTTPS:

cat > ~/docker-web/nginx/conf.d/default.conf <<'EOF'
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    root /var/www/html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?|ttf|svg|eot)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location ~ /\.ht {
        deny all;
    }
}
EOF

5.2 使用 acme.sh 自动获取 Let’s Encrypt 证书

不需要在容器内获取证书,在宿主机上使用 acme.sh,然后将证书挂载到 Nginx 容器即可:

# 安装 acme.sh
curl https://get.acme.sh | sh

# 设置默认 CA 为 Let's Encrypt
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

# 申请证书(需要先确保 80 端口已指向你的 VPS)
~/.acme.sh/acme.sh --issue -d yourdomain.com -d www.yourdomain.com --standalone

# 将证书复制到 Nginx SSL 目录
~/.acme.sh/acme.sh --install-cert -d yourdomain.com \
    --cert-file ~/docker-web/nginx/ssl/fullchain.pem \
    --key-file ~/docker-web/nginx/ssl/privkey.pem \
    --reloadcmd "docker exec web-nginx nginx -s reload"

acme.sh 会自动安装 cron 任务,每 60 天续期一次。配合上面的 --reloadcmd,续期后自动通知容器内的 Nginx 重新加载证书,整个过程完全自动化。

六、一键启动并部署 WordPress

所有配置文件就位后,启动整个服务栈:

cd ~/docker-web

# 首次构建 PHP 镜像并启动所有服务
docker compose up -d --build

# 查看运行状态
docker compose ps

# 查看启动日志
docker compose logs -f

# 下载 WordPress 并解压到 www 目录
cd www
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz --strip-components=1
rm latest.tar.gz
cd ..

# 设置正确的权限(PHP-FPM 需要 www-data 用户)
docker exec web-php chown -R www-data:www-data /var/www/html

启动完成后,访问 https://yourdomain.com 即可看到 WordPress 安装向导。数据库连接信息填写:

  • 数据库主机: mysql(Docker 内部网络通过服务名解析)
  • 数据库名: wordpress(与 .env 中 MYSQL_DATABASE 一致)
  • 数据库用户: wpuser(与 .env 中 MYSQL_USER 一致)
  • 数据库密码: 对应 .env 中的 MYSQL_PASSWORD

七、容器日常运维必备命令

掌握以下常用命令,日常维护不再犯难:

操作 命令 说明
查看运行中的容器 docker compose ps 显示所有容器状态
查看各容器日志 docker compose logs -f 跟踪实时日志输出
查看单个容器日志 docker logs web-nginx --tail 100 只看 Nginx 的最后 100 行
重启单个服务 docker compose restart nginx 只重启 Nginx,不影响其他服务
进入容器内操作 docker exec -it web-php bash 登录 PHP 容器 Shell
更新镜像并重建 docker compose pull && docker compose up -d 更新全部服务到最新版
停止并删除所有容器 docker compose down 数据卷中的数据保留
彻底清理 docker compose down -v ⚠️ 会删除数据卷中的数据

八、备份与迁移最佳实践

容器化方案的备份核心是:备份 docker-compose.yml、配置文件目录、数据库 dump 文件。相比传统 LNMP 环境需要从各个系统目录扒文件,这个流程简洁清晰得多:

#!/bin/bash
# backup.sh - 一键备份脚本
BACKUP_DIR="/root/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR

# 1. 备份数据库
docker exec web-mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \
    --all-databases --single-transaction > $BACKUP_DIR/db_${DATE}.sql

# 2. 打包配置文件和网站数据
tar -czf $BACKUP_DIR/docker-web_${DATE}.tar.gz \
    -C /root docker-web/.env \
    -C /root docker-web/docker-compose.yml \
    -C /root docker-web/nginx/conf.d \
    -C /root docker-web/nginx/ssl \
    -C /root docker-web/php/Dockerfile \
    -C /root docker-web/www/wp-content

# 3. 同步到远程备份或对象存储(可选)
# rclone copy $BACKUP_DIR remote:backups/

# 4. 保留最近 7 天,删除更早的备份
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete

echo "备份完成: $BACKUP_DIR/docker-web_${DATE}.tar.gz"

迁移到新 VPS 时,只需在新机器上安装 Docker,将备份的 tar.gz 解压,运行 docker compose up -d,再恢复数据库即可,全过程不超过 10 分钟。

九、性能优化与安全加固

9.1 容器资源限制

在一台低配 VPS 上运行多个容器时,强烈建议给每个容器分配资源上限,防止某个服务突发内存泄漏拖垮整个宿主机:

services:
  mysql:
    image: mysql:8.0
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    # ... 其余配置保持不变

9.2 网络安全最佳实践

  • 对外只暴露 80/443:MySQL、Redis 等内部服务不应该绑定到宿主机端口,只通过 Docker 内部网络通信。前面 docker-compose.yml 的设计已经做到了这一点——没有向宿主机暴露 3306 或 6379 端口。
  • 使用非 root 运行容器:在 Dockerfile 中可以通过 USER 指令切换到普通用户运行,降低容器逃逸风险。PHP-FPM 的 Alpine 镜像默认以 www-data 运行,已经是一个好基础。
  • 定期更新基础镜像:每个月运行一次 docker compose pull && docker compose up -d,确保使用最新的安全补丁。
  • 启用 Docker 内容信任:export DOCKER_CONTENT_TRUST=1 让 Docker 只拉取经过签名的镜像。

9.3 Nginx 安全头配置

在 Nginx 配置中添加安全响应头,提升站点安全性得分:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

十、常见问题与排错指南

10.1 容器启动后立即退出

最常见的原因是端口冲突或配置文件语法错误。排查方法:

# 查看容器退出后的日志
docker compose logs nginx
docker compose logs php

# 检查端口是否被占用
sudo lsof -i :80
sudo lsof -i :443

# 测试 Nginx 配置是否正确(进入容器内)
docker exec web-nginx nginx -t

10.2 数据库连接失败

WordPress 安装时提示 “数据库连接错误”,通常的原因是:

  • 数据库主机名错误:在 WordPress 中数据库主机填 mysql 而不是 localhost127.0.0.1,因为这是 Docker 内部网络的服务名。
  • MySQL 容器未就绪:PHP 容器启动时 MySQL 可能还没完成初始化。可以在 docker-compose.yml 中添加健康检查机制。
  • 字符集问题:确保 MySQL 配置文件或启动参数中设置了 character-set-server=utf8mb4

10.3 上传文件大小限制

如果 WordPress 后台无法上传大文件,需要同时修改三个地方:

  • Nginx 层面:在 server 块中添加 client_max_body_size 64M;
  • PHP 层面:设置 upload_max_filesizepost_max_size(已在 Dockerfile 环境变量中设置)
  • WordPress 层面:在 wp-config.php 中添加 define('WP_MEMORY_LIMIT', '256M');

十一、进阶:用 Traefik 替代 Nginx 实现自动反向代理

如果你管理多个站点,手动维护 Nginx 配置文件会很繁琐。Traefik 是一个云原生反向代理,能够自动发现 Docker 容器并为其分配域名和 SSL 证书。只需在容器 label 中声明域名,Traefik 会自动处理路由和证书申请:

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entryPoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./traefik/letsencrypt:/letsencrypt"
    labels:
      - "traefik.enable=true"

  wordpress:
    image: wordpress:6-php8.2-fpm-alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.wordpress.rule=Host(\`blog.yourdomain.com\`)"
      - "traefik.http.routers.wordpress.tls=true"
      - "traefik.http.routers.wordpress.tls.certresolver=letsencrypt"
    # ... 其他配置

此后每增加一个站点,只需在容器 label 中添加对应域名规则,Traefik 会自动完成证书申请和反向代理配置,真正实现”零接触”运维。

总结

本文从零开始详细介绍了如何在 VPS 上使用 Docker + Docker Compose 部署高可用的 Web 应用栈。从 Docker 安装、服务栈搭建、SSL 自动配置,到日常运维、备份迁移和安全加固,每一个环节都给出了可执行的代码和配置。

相较于传统的 LNMP 一键包方案,容器化部署虽然初期需要多花半小时搭建 docker-compose.yml,但在后续的维护、升级和迁移过程中节省的时间是数倍甚至数十倍的。特别是当你需要在同一台 VPS 上运行多个不同技术栈的站点时,Docker 的环境隔离优势更加明显。

如果你还在犹豫是否要切换到容器化方案,建议先花一个下午照着本文的步骤在测试环境中跑一遍,感受一下”docker compose up -d”一键启动整个应用的畅快感。一旦上手,你就会发现再也回不去传统的手动配置方式了。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » Docker + Docker Compose 实战:在VPS上用容器化方案部署高可用Web应用的完整指南
分享到: 更多 (0)