Docker: Phase 1 Mini Project

最后更新:2026-08-26

现在把前 5 课的知识串起来——从零部署一个完整的 LEMP 栈(Linux + Nginx + MySQL + PHP),这是 Phase 1 的综合验收项目。

1. 你将学到


2. 一个后端开发者的真实故事

(1) 痛点:手动搭建开发环境耗时半天

Alice 要为公司的内部管理系统搭建开发环境:Nginx 提供静态服务、PHP-FPM 处理动态请求、MySQL 存储数据。手动安装需要:config Nginx 虚拟主机、安装 PHP-FPM 和扩展、安装并初始化 MySQL、配置三者之间的通信路径。每一步都可能踩坑,同事用了一天才搭好。

(2) Docker 多容器部署的解法

Alice 用 Docker 在 10 分钟内搭建了完整的 LEMP 栈——三个容器、一个网络、两个数据卷。

BASH
# Architecture overview: Nginx → PHP-FPM → MySQL
docker network create lemp-net
docker volume create mysql-data

(3) 收益:10 分钟 vs 半天

手动部署半天(且不可复用),Docker 部署 10 分钟(脚本可复用,团队共享)。新成员入职从 1 天环境搭建缩短到 15 分钟。


3. LEMP 架构设计

(1) 架构总览

100%
graph TB
    USER["User Browser<br/>:8080"] --> NGX["Nginx Container<br/>:80<br/>Static files + PHP proxy"]
    NGX -->|"FastCGI :9000"| PHP["PHP-FPM Container<br/>:9000<br/>PHP processing"]
    PHP -->|"MySQL :3306"| DB["MySQL Container<br/>:3306<br/>Database"]
    DB --> VOL["Named Volume<br/>mysql-data<br/>Persistent storage"]
    PHP --> PHPV["Bind Mount<br/>./src → /var/www/html<br/>PHP source code"]
    NGX --> NGXV["Bind Mount<br/>./nginx.conf<br/>Nginx config"]

(2) 手动部署 vs Docker 部署对比

维度 手动部署 Docker 部署
耗时 4-8 小时 10 分钟
可重复性 低(依赖操作者经验) 高(脚本/命令可复用)
可移植性 低(绑定特定 OS) 高(镜像跨平台一致)
环境隔离 无(共享系统环境) 完全隔离
回滚 困难(卸载重装) 简单(删除容器重建)
团队共享 写文档+照做 共享命令/脚本

4. 准备工作:网络与卷

(1) 创建自定义网络

容器间通信需要一个自定义 bridge 网络——默认 bridge 网络不支持容器名 DNS 解析。

BASH
# Create a custom bridge network for LEMP stack
docker network create lemp-net

# Verify
docker network ls | grep lemp-net

(2) 创建数据卷

MySQL 数据需要持久化——容器删除后数据不能丢失。

BASH
# Create a named volume for MySQL data
docker volume create mysql-data

# Verify
docker volume ls | grep mysql-data

(3) 卷挂载策略对比

策略 syntax 适用场景 持久性
Named Volume -v mysql-data:/var/lib/mysql database、持久化存储 ✅ 容器删除后保留
Bind Mount -v ./src:/var/www/html 开发时代码同步 ✅ 映射主机目录
tmpfs --tmpfs /tmp 临时数据、敏感信息 ❌ 容器停止后消失

5. deploy MySQL 容器

▶ 示例:启动 MySQL 容器(难度⭐⭐)

BASH
# Start MySQL 8.0 with persistent storage
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0
💡 提示: 先启动 MySQL,等它初始化完成(约 30 秒)再启动其他容器。可以用 docker logs -f lemp-mysql 观察初始化进度,看到 "ready for connections" 就说明初始化完成。

(1) 验证 MySQL 运行

BASH
# Check MySQL is running
docker ps --filter name=lemp-mysql

# Connect to MySQL to verify
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e "SELECT 1;"

6. 部署 PHP-FPM 容器

(1) 准备 PHP 代码

先创建一个 PHP 测试文件和数据库连接测试文件。

BASH
# Create project directory
mkdir -p lemp-project/src

# Create PHP info page
cat > lemp-project/src/info.php << 'EOF'
<?php
phpinfo();
?>
EOF

# Create database connection test
cat > lemp-project/src/db-test.php << 'EOF'
<?php
$host = 'lemp-mysql';
$db   = 'appdb';
$user = 'appuser';
$pass = 'apppass';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "Connected to MySQL successfully!<br>";
    echo "MySQL version: " . $pdo->query("SELECT VERSION()")->fetchColumn();
} catch (PDOException $e) {
    echo "Connection failed: " . $e->getMessage();
}
?>
EOF

▶ 示例:启动 PHP-FPM 容器(难度⭐⭐)

BASH
# Start PHP-FPM container
docker run -d \
  --name lemp-php \
  --network lemp-net \
  -v $(pwd)/lemp-project/src:/var/www/html \
  --restart=unless-stopped \
  php:8.2-fpm
📌 重点: -v $(pwd)/lemp-project/src:/var/www/html 是 Bind Mount——本机代码目录映射到容器内,修改本机文件后容器内立即生效,适合开发调试。


7. 部署 Nginx 容器

(1) 准备 Nginx 配置

Nginx 需要配置 FastCGI 代理,将 PHP 请求转发给 PHP-FPM 容器。

BASH
# Create Nginx configuration
cat > lemp-project/nginx.conf << 'EOF'
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

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

    location ~ \.php$ {
        fastcgi_pass lemp-php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}
EOF
📌 重点: fastcgi_pass lemp-php:9000 中的 lemp-php 是 PHP-FPM 容器名。因为它们在同一自定义网络 lemp-net 中,Docker DNS 自动将容器名解析为容器 IP。

▶ 示例:启动 Nginx 容器(难度⭐⭐)

BASH
# Start Nginx container
docker run -d \
  --name lemp-nginx \
  --network lemp-net \
  -p 8080:80 \
  -v $(pwd)/lemp-project/src:/var/www/html \
  -v $(pwd)/lemp-project/nginx.conf:/etc/nginx/conf.d/default.conf \
  --restart=unless-stopped \
  nginx:1.25-alpine

8. 验证与测试

▶ 示例:访问 PHP 页面验证(难度⭐)

BASH
# Test PHP info page
curl http://localhost:8080/info.php | head -5

# Test database connection
curl http://localhost:8080/db-test.php
💻 输出:

TEXT 📖 仅展示
# curl http://localhost:8080/db-test.php
Connected to MySQL successfully!
MySQL version: 8.0.35
✅ 检查点: 看到 "Connected to MySQL successfully!" 说明三容器协作成功:Nginx → PHP-FPM → MySQL。

(1) 完整验证清单

检查项 命令 预期结果
三个容器都在运行 docker ps 3 个容器状态为 Up
PHP 页面正常 curl localhost:8080/info.php 返回 PHP 配置信息
数据库连接正常 curl localhost:8080/db-test.php "Connected successfully"
容器间 DNS 解析 docker exec lemp-nginx ping -c 2 lemp-php 能 ping 通
数据卷存在 docker volume ls mysql-data 在列表中

9. 数据持久化验证

▶ 示例:模拟 MySQL 容器崩溃(难度⭐⭐⭐)

BASH
# 1. Create a test table with data
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "CREATE TABLE test_items (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100)); INSERT INTO test_items (name) VALUES ('Alice'), ('Bob');"

# 2. Verify data exists
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "SELECT * FROM test_items;"

# 3. Stop and remove the MySQL container
docker stop lemp-mysql && docker rm lemp-mysql

# 4. Recreate MySQL container with the same volume
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0

# 5. Wait for MySQL to start, then verify data is still there
docker exec -it lemp-mysql mysql -uappuser -papppass appdb -e \
  "SELECT * FROM test_items;"
💻 输出:

TEXT 📖 仅展示
+----+-------+
| id | name  |
+----+-------+
|  1 | Alice |
|  2 | Bob   |
+----+-------+
✅ 检查点: 容器删除重建后数据仍然存在——Named Volume 的持久化生效了。


10. 容器排序与依赖策略

策略 method 可靠性
手动按序启动 MySQL → PHP → Nginx 中(依赖经验)
健康检查等待 等 MySQL 就绪再启动 PHP 高(自动化)
restart 策略 --restart=on-failure 让失败的服务自动重试 中(不保证顺序)
Docker Compose depends_on + 健康检查(Phase 3 学习) 高(声明式)
💡 提示: 本课用手动启动顺序。Phase 3 会学到 Docker Compose 的 depends_on,实现自动化依赖管理。


11. 完整示例:一键部署 LEMP 栈脚本

BASH
# ============================================
# Complete walkthrough: Deploy LEMP stack from scratch
# Covers: network, volume, MySQL, PHP, Nginx
# ============================================

# --- Setup ---
# Create network and volume
docker network create lemp-net
docker volume create mysql-data

# Create project directory and files
mkdir -p lemp-project/src

cat > lemp-project/src/index.php << 'PHPEOF'
<?php
$host = 'lemp-mysql';
$db   = 'appdb';
$user = 'appuser';
$pass = 'apppass';
try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    echo "<h1>LEMP Stack is working!</h1>";
    echo "<p>MySQL version: " . $pdo->query("SELECT VERSION()")->fetchColumn() . "</p>";
} catch (PDOException $e) {
    echo "<h1>Database connection failed</h1><p>" . $e->getMessage() . "</p>";
}
?>
PHPEOF

cat > lemp-project/nginx.conf << 'NGINXEOF'
server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_pass lemp-php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
NGINXEOF

# --- Deploy ---
# 1. Start MySQL (must be first)
docker run -d \
  --name lemp-mysql \
  --network lemp-net \
  -e MYSQL_ROOT_PASSWORD=rootsecret \
  -e MYSQL_DATABASE=appdb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -v mysql-data:/var/lib/mysql \
  --restart=unless-stopped \
  mysql:8.0

# 2. Wait for MySQL to initialize (~30 seconds)
echo "Waiting for MySQL to initialize..."
sleep 30

# 3. Start PHP-FPM
docker run -d \
  --name lemp-php \
  --network lemp-net \
  -v $(pwd)/lemp-project/src:/var/www/html \
  --restart=unless-stopped \
  php:8.2-fpm

# 4. Start Nginx
docker run -d \
  --name lemp-nginx \
  --network lemp-net \
  -p 8080:80 \
  -v $(pwd)/lemp-project/src:/var/www/html \
  -v $(pwd)/lemp-project/nginx.conf:/etc/nginx/conf.d/default.conf \
  --restart=unless-stopped \
  nginx:1.25-alpine

# --- Verify ---
echo "Verifying deployment..."
docker ps
curl -s http://localhost:8080/index.php
💻 输出(节选):

TEXT 📖 仅展示
# docker ps
CONTAINER ID   IMAGE            STATUS         PORTS                  NAMES
c1d2e3f4       nginx:1.25-alpine  Up 5 min      0.0.0.0:8080->80/tcp   lemp-nginx
b2c3d4e5       php:8.2-fpm      Up 5 min       9000/tcp               lemp-php
a1b2c3d4       mysql:8.0        Up 5 min       3306/tcp, 33060/tcp    lemp-mysql

# curl http://localhost:8080/index.php
<h1>LEMP Stack is working!</h1><p>MySQL version: 8.0.35</p>

❓ 常见问题

Q 为什么分开启动不用 Compose?
A 这是 Phase 1 的综合实践,目的是让你理解每个容器是如何独立创建和连接的。Phase 3 会学到 Docker Compose,用一个 YAML 文件替代这些 docker run 命令。先手动再自动,理解更深。
Q Nginx 怎么连接 PHP-FPM?
A 通过 FastCGI 协议。Nginx 配置中 fastcgi_pass lemp-php:9000 指向 PHP-FPM 容器。lemp-php 是容器名,在同一自定义网络中 Docker DNS 自动解析为容器 IP,无需硬编码 IP 地址。
Q 容器重启后数据还在吗?
A 用 Named Volume(-v mysql-data:/var/lib/mysql)挂载的数据在容器删除后仍然存在。但 Bind Mount 的源代码(-v ./src:/var/www/html)是映射到主机目录的,只要主机目录不删,代码也不会丢。
Q 怎么查哪个容器报错了?
A 三步排查法:① docker ps -a 看哪个容器状态不是 Up;② docker logs <container> 查看错误日志;③ docker inspect <container> --format='{{.State.ExitCode}}' 查看退出码。常见:MySQL 未就绪时 PHP 连接报错,等 MySQL 初始化完成即可。
Q 多个容器日志怎么一起看?
A docker compose logs(Phase 3 学到)可以聚合查看。目前可用:① 分别 docker logs 每个容器;② 用 docker logs -f 开多个终端窗口同时监控;③ 将日志重定向到文件后合并查看。

📖 小节


📝 作业

  1. 基础题(难度⭐):从零部署 LEMP 栈,访问 http://localhost:8080/index.php 确认三个容器协作正常。
  2. 进阶题(难度⭐⭐):模拟 MySQL 容器崩溃(docker stop lemp-mysql && docker rm lemp-mysql),重建后验证数据库数据是否持久化保留。
  3. 挑战题(难度⭐⭐⭐):添加 Adminer(数据库管理工具)容器连接到 MySQL,用 Web 界面管理数据库。提示:docker run -d --name adminer --network lemp-net -p 8081:8080 adminer,访问 http://localhost:8081,用 appuser/apppass 登录。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏