前言
最近一段时间接了个项目一直在忙碌,遇到了一个非常无语的问题,因为该项目的测试服务器及环境是由甲方人员进行购买、搭建环境,我在本地开发后推到测试服务器上面进行构建测试、由于环境不一致的问题导致产生了很多问题,出现了经典的 在我机器上就可以运行没问题
的人生哲理......我滴妈 不想吐槽了。
所以说。在本地和测试环境中,使用 Docker 来安装和管理服务已经成为最简单、最常见的方式。通过 docker compose up -d
,我们可以一键启动所有依赖服务,提高开发和测试效率。此外,结合 Dockerfile 进行环境自定义,不仅可以实现个性化配置,还能方便团队协作,确保所有成员的环境一致。
Docker
平常情况下当我们入职新公司拿到新电脑的时候 第一步就是安装自己的本地开发环境,无论是Windows环境的安装包还是Mac的brew安装 都需要我们去一步一步手动安装 Nginx、MySQL、Redis、MQ 等服务,并配置各种依赖。这种安装方式无法保证我们一次性顺利安装成功 可能会遇到一些问题导致我们折腾一上午或者一天,这对于我们这种 “经验丰富”的牛马来说是不能容忍的。 那么使用docker安装的好处就体现出来了。
无论我们使用的是Windows系统还是Mac系统我们都可以通过安装Docker Desktop,它提供了简单易用的图形化界面,并且内置了 Docker Engine 和 Docker CLI,适用于本地开发和测试环境。
Docker Desktop安装方式我就不介绍了 不了解的可以参考其他关于介绍Docker Desktop的文章进行了解安装。
验证安装 打开终端(Terminal),运行:
docker -v
看到 Docker 版本信息即安装成功。
使用 Docker 单独安装环境
这里举个例子 比如我们有个项目需要安装php开发环境。我需要安装php、mysql、nginx、redis、rabbitmq这些服务。
1. 安装 PHP
PHP 运行环境可以通过官方提供的 Docker 镜像来安装:
docker pull php:8.2-fpm
然后运行 PHP 容器:
docker run -d --name php-container -p 9000:9000 -v $(pwd)/php:/var/www/html php:8.2-fpm
-
-d
:后台运行容器 -
--name php-container
:指定容器名称 -
-p 9000:9000
:映射 PHP-FPM 端口 -
-v $(pwd)/php:/var/www/html
:将本地php
目录挂载到容器的/var/www/html
,便于开发
容器启动后就是这样的。
可以进入 PHP 容器,运行 php -v
查看版本:
docker exec -it php-container php -v
2. 安装 MySQL
我们可以通过 Docker 轻松安装 MySQL 5.7:
docker run -d --name mysql-container -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:5.7
-
-e MYSQL_ROOT_PASSWORD=123456
:设置 root 用户密码 -
-p 3306:3306
:映射数据库端口 -
--name mysql-container
:指定容器名称
验证 MySQL 运行
docker exec -it mysql-container mysql -uroot -p
输入 123456
进入 MySQL 命令行。
3. 安装 Nginx
docker run -d --name nginx-container -p 80:80 -v $(pwd)/nginx/conf.d:/etc/nginx/conf.d -v $(pwd)/php:/var/www/html nginx
-
-p 80:80
:映射 Web 端口 -
-v $(pwd)/nginx/conf.d:/etc/nginx/conf.d
:挂载本地 Nginx 配置 -
-v $(pwd)/php:/var/www/html
:确保 Nginx 可以访问 PHP 代码目录
测试 Nginx
curl http://localhost
如果返回默认的 Nginx 页面,说明安装成功。
4. 安装 Redis
Redis安装方式如下:
docker run -d --name redis-container -p 6379:6379 redis:5.0
-
-p 6379:6379
:映射 Redis 端口 -
--name redis-container
:指定容器名称
连接 Redis
docker exec -it redis-container redis-cli
输入 ping
,如果返回 PONG
,说明 Redis 运行正常。
5. 安装 RabbitMQ
RabbitMQ安装方法如下:
docker run -d --name rabbitmq-container -p 5672:5672 -p 15672:15672 rabbitmq:latest
-
-p 5672:5672
:RabbitMQ 消息端口 -
-p 15672:15672
:RabbitMQ Web 管理界面端口
访问 Web 管理界面 浏览器打开 http://localhost:15672/
,默认用户名密码为 guest/guest
单独docker run的问题
上面我们介绍了如何使用 docker run
命令分别安装 PHP、MySQL、Nginx、Redis 和 RabbitMQ。这种方式虽然能让我们快速启动各个服务,但能发现感觉好麻烦,大概总结一下麻烦点:
1. 多个容器管理麻烦
- 当我们需要启动多个服务时,每次都要运行多个
docker run
命令,手动指定端口、环境变量、挂载目录等。 - 例如,如果要启动 PHP、MySQL、Nginx、Redis 和 RabbitMQ,我们需要运行 5 条命令,每次启动、停止、删除都要手动执行,费时费力。
2. 容器之间的网络通信需要手动配置
-
直接使用
docker run
启动容器后,它们默认属于不同的网络,彼此之间无法直接访问,必须手动创建 Docker 网络:docker network create my-network
然后在每个
docker run
命令中加上--network my-network
,这样容器之间才能互相通信,配置过程较为繁琐。
3. 复杂的环境变量和配置管理
- 我们也许需要为每个服务设置环境变量(如 MySQL 的
MYSQL_ROOT_PASSWORD
、Redis 的配置等),如果用docker run
,这些变量必须手动传递,而且不方便维护。 - 配置文件(如
nginx.conf
、PHP 的php.ini
、MySQL 的my.cnf
)需要挂载到正确的路径,使用docker run
逐个配置很麻烦。
4. 版本控制和团队协作困难
- 在团队开发中,不同开发人员的环境可能存在差异,比如某人使用 MySQL 5.7,而我自己用了 MySQL 8.0,导致程序行为不同。
- 直接用
docker run
,每个人都要手动执行相同的命令,容易出错,也难以保证所有人的开发环境完全一致。
Docker Compose 的解决方案
这时候就不得不说使用Docker Compose的好处了。Docker Compose 是通过一个 docker-compose.yml
文件 定义所有服务,并提供了一键启动的能力:
-
统一管理所有服务,只需一个命令即可启动/停止所有容器:
docker compose up -d
-
自动创建网络,让所有容器在同一个网络下自由通信,无需手动配置。
-
更方便的配置管理,所有环境变量、挂载路径都可以直接写入
docker-compose.yml
,可读性更好。 -
易于版本控制和团队协作,只需共享
docker-compose.yml
文件,所有人都能获得相同的环境。
示例:使用 Docker Compose 启动 PHP + MySQL + Nginx + Redis + RabbitMQ
用Docker Compose来启动多项服务的话 建议最好设置一个目录来进行统一的文件映射与存放。 比如我本地的项目目录结构是这样的:
project-root/
│── docker-compose.yml
│── php/
│── mysql/
│ ├── my.cnf
│── nginx/
│ ├── conf.d/
│── redis/
│── rabbitmq/
│── wwwroot/
-
php/
:PHP 代码目录 -
mysql/my.cnf
:MySQL 配置文件 -
nginx/conf.d/
:Nginx 配置目录 -
redis/
和rabbitmq/
只是占位符目录 -
wwwroot/
项目存放目录
然后我们这次用新建 docker-compose.yml
的方式来一键启动上面我们安装的服务。比如我们新建 docker-compose.yml
,然后填入以下内容:
services:
php:
image: php:8.2-fpm
container_name: php-container
volumes:
- ./wwwroot:/var/www/html
networks:
- my-network
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
container_name: mysql-container
restart: always
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mydb
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/my.cnf
networks:
- my-network
nginx:
image: nginx:latest
container_name: nginx-container
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./wwwroot:/var/www/html
networks:
- my-network
depends_on:
- php
redis:
image: redis:5.0
container_name: redis-container
restart: always
ports:
- "6379:6379"
networks:
- my-network
rabbitmq:
image: rabbitmq:latest
container_name: rabbitmq-container
restart: always
ports:
- "5672:5672"
- "15672:15672"
networks:
- my-network
networks:
my-network:
driver: bridge
在 docker-compose.yml
中,每个服务(service)都有不同的配置项,如 image
、container_name
、volumes
、networks
、depends_on
等。下面我们简单逐个解析它们的作用和使用方式。
1. image
:指定使用的 Docker 镜像
image: php:8.2-fpm
-
作用:指定容器使用哪个 Docker 镜像。
-
示例:
-
php:8.2-fpm
:使用官方 PHP 8.2-FPM 镜像。 -
mysql:5.7
:使用官方 MySQL 5.7 镜像。 -
nginx:latest
:使用最新版本的 Nginx 镜像。
-
-
如果本地没有这个镜像,Docker 会自动从 Docker Hub 拉取。
2. container_name
:指定容器名称
container_name: php-container
-
作用:为容器指定一个固定的名称,便于管理和操作。
-
示例:
-
container_name: mysql-container
→ 我们可以用docker ps
看到容器的名称是mysql-container
。 - 这样我们可以用
docker exec -it mysql-container bash
进入容器,而不用记住随机生成的容器 ID。
-
⚠ 注意:如果不指定 container_name
,Docker 会自动给容器分配一个随机名称,比如 adoring_turing
这样的名字。
3. volumes
:挂载本地文件或目录
volumes:
- ./wwwroot:/var/www/html
-
作用:将 宿主机的目录(或文件) 挂载到容器的指定目录,方便开发和数据持久化。
-
格式:
volumes: - 宿主机路径:容器内部路径
-
示例:
-
./wwwroot:/var/www/html
→ 让 PHP 容器 使用本地wwwroot
目录作为代码目录。这样我们本地修改wwwroot
里的文件,容器里会同步更新。 -
./mysql/data:/var/lib/mysql
→ 让 MySQL 容器 将数据库数据存储在./mysql/data
目录,防止数据丢失。 -
./nginx/conf.d:/etc/nginx/conf.d
→ 让 Nginx 容器 使用本地nginx/conf.d/
目录中的配置文件,方便修改 Nginx 配置。
-
⚠ 注意:如果不使用 volumes
,容器中的数据会丢失,因为容器被删除后,它内部的文件也会被清空
4. networks
:管理容器间的网络
networks:
- my-network
-
作用:将容器加入同一个网络,让它们可以互相通信,而不用手动创建网络。
-
示例:
networks: my-network: driver: bridge
-
这样所有在
my-network
网络中的容器都可以通过 容器名称 互相访问。例如:- PHP 可以通过
mysql-container
连接 MySQL,而不用localhost
。 - Nginx 可以通过
php-container
访问 PHP-FPM,而不用127.0.0.1
。
- PHP 可以通过
-
⚠ 如果不指定 networks
,Docker 默认会创建一个独立的网络,每个容器只能用 localhost
访问自己,不能访问其他容器。
5. depends_on
:指定容器依赖关系
depends_on:
- mysql
- redis
-
作用:确保
php
容器 在mysql
和redis
之后启动。 -
示例:
php: depends_on: - mysql - redis
- 这样
php
不会先于mysql
和redis
启动,可以避免 PHP 启动时 MySQL 还没准备好导致连接失败的情况。
- 这样
⚠ 注意:
-
depends_on
只保证 容器启动顺序,但 不保证服务内部完全就绪。 -
更可靠的方式 是在
php
容器启动时,加入一个等待逻辑,确保mysql
和redis
可用,比如使用wait-for-it.sh
脚本。
总结一下
配置项 | 作用 | 示例 |
---|---|---|
image |
指定 Docker 镜像 | php:8.2-fpm |
container_name |
设定容器名称 | php-container |
volumes |
挂载本地目录到容器 | ./wwwroot:/var/www/html |
networks |
让容器互相通信 | my-network |
depends_on |
设置容器依赖关系 | php -> mysql, redis |
启动项目
在 docker-compose.yml
所在目录下,运行:
docker compose up -d
这将:
- 拉取所需镜像
- 启动 PHP、MySQL、Nginx、Redis 和 RabbitMQ
- 自动创建网络
my-network
,让各个服务互相通信
停止 & 删除容器
docker compose down
这将停止并移除所有容器。
上面我们已经使用 docker-compose.yml
成功编排了 PHP、MySQL、Nginx、Redis 和 RabbitMQ,并且能够一键启动所有服务。但是,虽然 环境的版本一致了,但仍然会遇到一些 额外的问题,比如我们需要进行 PHP 扩展的安装。
问题:为什么仅使用 docker-compose.yml
还不够?
让我们来看看,在 docker-compose.yml
方式下,可能会出现哪些问题:
1. 需要手动进入容器安装 PHP 扩展
当我们启动 PHP 容器后,默认的 php:8.2-fpm
镜像 只包含 PHP 运行环境,但实际项目可能需要用到 pdo_mysql
、gd
、redis
、bcmath
等扩展。例如,我们需要执行:
docker exec -it php-container bash
然后在容器内手动安装:
docker-php-ext-install pdo_mysql gd bcmath
docker-php-ext-enable redis
这样虽然可以解决问题,但 每次换一台机器都要手动操作一次,非常麻烦。
2. 团队协作时,每个人都要重复安装
假设我们的同事也使用了我们的 docker-compose.yml
启动环境:
docker compose up -d
他虽然能启动 PHP 容器,但仍然需要手动进入容器安装扩展。如果 有 10 个人 都要这么做,那就是 10 次重复劳动,这不仅费时,还可能导致版本不一致(有些人可能少装了某个扩展)。
3. 难以维护,容易出错
- 如果某人忘记安装某个扩展,可能会导致 代码无法运行,然后需要额外的时间排查问题。
- 新成员加入团队时,还需要花额外的时间指导他们如何手动安装扩展,增加了学习成本。
解决方案:使用 Dockerfile
我们最终希望:
- PHP 镜像启动后,扩展就已经安装好了,不需要每个人手动操作。
- 所有人都能使用相同的 PHP 运行环境,包含所有必要的扩展。
- 一次配置,所有人受益,避免重复劳动。
👉 这就是 Dockerfile 的作用!我们可以使用 Dockerfile 定制自己的 PHP 镜像,在构建镜像时自动安装所有需要的扩展,从而彻底解决这个问题。
接下来,我们将使用 Dockerfile 来构建一个包含 PHP 扩展的自定义镜像,确保每个人启动 PHP 容器后,就能直接使用项目所需的环境!🚀
我们在项目的 php/
目录下,新建 Dockerfile
(不带扩展名),然后填入以下内容:
# 使用 php:8.2-fpm 作为基础镜像
FROM php:8.2-fpm
# 安装常见的扩展依赖(这适用于很多 PHP 扩展的依赖)
RUN apt-get update && apt-get install -y \
libzip-dev \
libxml2-dev \
libcurl4-openssl-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libmemcached-dev \
libssl-dev \
libssh2-1-dev \
libsodium-dev \
libicu-dev \
libmagickwand-dev \
gettext \
unzip \
&& rm -rf /var/lib/apt/lists/*
# 安装 PHP 扩展
RUN docker-php-ext-install -j$(nproc) pdo_mysql
# 安装并启用 Redis 扩展
RUN pecl install redis && docker-php-ext-enable redis
# 安装并启用 Memcached 扩展
RUN pecl install memcached && docker-php-ext-enable memcached
# 安装并启用 MongoDB 扩展
RUN pecl install mongodb && docker-php-ext-enable mongodb
# 安装并启用 Xdebug 扩展
RUN pecl install xdebug && docker-php-ext-enable xdebug
# 安装并启用 Opcache 扩展
RUN docker-php-ext-enable opcache
# 安装并启用 Swoole 扩展(如果需要)
RUN pecl install swoole && docker-php-ext-enable swoole
# 安装并启用 igbinary 扩展(用于更高效的序列化)
RUN pecl install igbinary && docker-php-ext-enable igbinary
# 安装并启用 GD 扩展
RUN docker-php-ext-install gd
# 安装并启用 PCNTL 扩展
RUN docker-php-ext-install pcntl
# 安装 Composer(PHP 依赖管理工具)
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# 设置 Composer 为全局可用
RUN ln -s /usr/local/bin/composer /usr/bin/composer
# 清理缓存以减小镜像大小
RUN apt-get clean
# 配置 Xdebug
RUN echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
echo "xdebug.client_port=9003" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
# 默认启用 OPcache
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
# 设置工作目录
WORKDIR /var/www/html
# 默认命令
CMD ["php-fpm"]
修改 docker-compose.yml
使用 Dockerfile
在 docker-compose.yml
中,我们需要 修改 PHP 的 image
配置,让它从 Dockerfile 构建自定义镜像,而不是直接拉取 php:8.2-fpm
。
services:
php:
build:
context: ./php # 指定 Dockerfile 所在目录
dockerfile: Dockerfile # 这个参数可以省略,默认就是 Dockerfile
container_name: php-container
volumes:
- ./wwwroot:/var/www/html # 挂载项目代码
networks:
- my-network
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
container_name: mysql-container
restart: always
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mydb
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/my.cnf
networks:
- my-network
nginx:
image: nginx:latest
container_name: nginx-container
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./wwwroot:/var/www/html # 让 Nginx 也能访问 PHP 代码
networks:
- my-network
depends_on:
- php
redis:
image: redis:5.0
container_name: redis-container
restart: always
ports:
- "6379:6379"
networks:
- my-network
rabbitmq:
image: rabbitmq:latest
container_name: rabbitmq-container
restart: always
ports:
- "5672:5672"
- "15672:15672"
networks:
- my-network
networks:
my-network:
driver: bridge
改动点
-
build:
-
替换
image: php:8.2-fpm
,改为:build: context: ./php dockerfile: Dockerfile
-
这样 Docker Compose 会在
php/
目录 下查找Dockerfile
并构建自定义镜像。
-
-
volumes:
- 仍然保持
./wwwroot:/var/www/html
,保证代码能够挂载到 PHP 容器中。
- 仍然保持
构建并运行容器
第一次运行(需要构建镜像)
docker compose up -d --build
-
--build
选项确保 重新构建 PHP 镜像,并安装扩展。
之后的启动(无需重建)
docker compose up -d
验证 PHP 是否安装了扩展
进入 PHP 容器:
docker exec -it php-container /bin/bash
然后运行:
php -m | grep -E "pdo_mysql|gd|bcmath|opcache|redis|memcached|mongodb|xdebug|swoole|igbinary"
如果输出如下:
说明所有扩展已正确安装!🎉
这样做的好处
✅ 解决了手动安装 PHP 扩展的问题,所有扩展在构建镜像时自动安装。
✅ 保证团队环境一致,不需要每个开发者手动安装扩展。
✅ 包含 Composer,方便管理 PHP 依赖。
✅ 支持 Xdebug 远程调试,方便开发和调试代码。
✅ 构建一次,所有人都可以直接使用相同的环境,提升开发效率。
后续优化(可选)
如果我们还想:
-
优化 PHP 配置,可以在
php/
目录下创建php.ini
,并在Dockerfile
里挂载:COPY php.ini /usr/local/etc/php/php.ini
-
减少镜像体积,可以在
RUN
语句后加上rm -rf /var/lib/apt/lists/*
来清理不必要的缓存。
除了 PHP,我们安装的 MySQL、Redis、Nginx 和 RabbitMQ 也可以使用 Dockerfile 进行 自定义构建 和 配置优化。
虽然 docker-compose.yml
可以直接拉取官方镜像,但通过 Dockerfile 自定义构建,我们可以实现:
-
定制化配置(如 MySQL 自定义
my.cnf
、Nginx 自定义nginx.conf
)。 - 安装额外插件(如 MySQL 额外扩展、Nginx 额外模块)。
- 统一环境管理,避免不同机器上手动修改配置导致的问题。
其他服务的Dockerfile我就不一一写示例了,总之我们可以通过类似的方式在 mysql/
、redis/
、nginx/
和 rabbitmq/
目录下创建各自的 Dockerfile
来完成自定义。 然后修改docker-compose.yml
对应的地方来进行重新构建启动。
到这里,我们已经: ✅ 了解了 docker-compose.yml
如何进行服务编排。
✅ 发现了 docker-compose.yml
的局限性(仍需手动安装 PHP 扩展)。
✅ 通过 Dockerfile 自定义了 PHP 运行环境(扩展已安装、环境已配置)。
✅ 了解了 MySQL、Redis、Nginx、RabbitMQ 也可以使用 Dockerfile 进行自定义构建,并列出了使用 Dockerfile 的优势。
其他
针对上面的一些示例。我再简单说一些可以完善的地方以供参考哈。
1. 如何优化 Docker 镜像大小?
目前我们的 Dockerfile
直接基于 php:8.2-fpm
,并安装了很多扩展。如果需要优化镜像大小,可以:
-
使用
multi-stage build
来减少不必要的组件(特别适用于构建 PHP 应用)。 -
减少
apt-get
安装后的缓存:RUN apt-get update && apt-get install -y \ some-packages \ && rm -rf /var/lib/apt/lists/* && apt-get clean
-
避免安装不必要的软件,尽量精简。
如果我们的 PHP 代码 最终是要在生产环境运行,那么可以用 更轻量的镜像,如 php:8.2-alpine
:
FROM php:8.2-fpm-alpine
这样可以减少很多无用的 Linux 库。
2. 如何管理环境变量?
在我们的 docker-compose.yml
里面的 MySQL 密码、数据库名是写死的:
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: mydb
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
但在 生产环境中,明文暴露密码是非常危险的!
建议改为 .env
文件管理环境变量:
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
然后创建 .env
文件:
MYSQL_ROOT_PASSWORD=your_secure_password
MYSQL_DATABASE=mydb
MYSQL_USER=myuser
MYSQL_PASSWORD=mypassword
这样可以 避免密码硬编码,并且更方便切换不同的环境配置。
3. 如何让 Nginx 正确解析 PHP 请求?
我们虽然在 docker-compose.yml
里定义了 Nginx 服务:
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
但如果 没有正确配置 nginx.conf
,Nginx 可能不会解析 PHP 文件。比如我们提供一个示例 nginx.conf
:
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ .php$ {
include fastcgi_params;
fastcgi_pass php-container:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
这样,Nginx 就能正确解析 .php
文件,并将请求转发到 php-container
。
4. 如何管理数据持久化?
目前 docker-compose.yml
里挂载了 mysql/data
目录:
volumes:
- ./mysql/data:/var/lib/mysql
但我们可以 进一步优化数据管理:
-
使用 Docker Volume,而不是直接挂载宿主机目录:
volumes: mysql-data: services: mysql: volumes: - mysql-data:/var/lib/mysql
这样即使
docker-compose down
,数据仍然保留在docker volume
里,而不会误删。 -
对 Redis 进行持久化(AOF 方式) :
redis: volumes: - ./redis/data:/data
确保 Redis 不会在容器重启后丢失数据。
5. 使用 Makefile
简化管理
比如我们现在启动项目需要运行:
docker compose up -d --build
如果要停止:
docker compose down
那我们其实可以创建一个 Makefile
,让这些命令更简洁:
up:
docker compose up -d --build
down:
docker compose down
logs:
docker compose logs -f
restart:
docker compose down && docker compose up -d --build
这样,执行 make up
就可以一键启动环境,比手动输入命令要方便很多。
6. 如何在 CI/CD 中使用 Docker Compose?
如果我们的团队需要在 CI/CD(持续集成/持续部署) 流程中使用 Docker,可以在 GitHub Actions 或 GitLab CI 里集成 docker-compose
,自动构建和部署服务。例如:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker
uses: docker/setup-buildx-action@v1
- name: Build and run services
run: |
docker compose up -d --build
这样,每次推送代码,GitHub Actions 就会 自动启动 Docker 容器 进行测试。
7. 如何优化 Xdebug 远程调试?
因为上面我们在 Dockerfile
里已经安装了 Xdebug:
RUN pecl install xdebug && docker-php-ext-enable xdebug
但如果本地开发时 想用 VS Code 进行远程调试,需要添加 .vscode/launch.json
:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
}
这样,我们就可以在 VS Code 里直接调试 PHP 代码 了。
8. 生产环境 vs 开发环境
目前我们的 Dockerfile
适用于 开发环境,但在 生产环境,也许我们可能希望:
-
去掉 Xdebug(提高性能)。
-
使用
php:8.2-fpm-alpine
,减少镜像大小。 -
增加
healthcheck
来自动检测 MySQL、Redis 是否存活:mysql: healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 30s timeout: 10s retries: 3
这样,容器会自动检测 MySQL 是否可用,如果失败,Docker 会自动重启它。
最后
通过上面的示例,我们可以看到,使用 Docker 及 Docker Compose 可以极大地简化本地和测试环境的搭建,让我们能够快速启动完整的 PHP 运行环境,包括 PHP、MySQL、Nginx、Redis、RabbitMQ 等常见服务。同时,使用 Dockerfile 进一步定制化,使得每个开发人员的环境都保持一致,避免了手动安装扩展、配置环境的麻烦。
然而,我要说下 上面的这些示例 主要适用于本地开发和测试环境。当我们进入 正式生产环境 时,就需要考虑更严谨的 Dockerfile 编写和镜像构建,比如:
-
精简 Docker 镜像,使用
php:8.2-fpm-alpine
以减少不必要的软件包,优化启动速度和安全性。 -
环境变量管理,使用
.env
文件或 Kubernetes Secret 来存储敏感信息,避免密码硬编码。 - 数据持久化和备份,使用 Docker Volumes 或 NFS 来管理数据库、Redis 缓存等关键数据的存储,确保高可用性。
-
日志管理,使用
docker logs
并结合 ELK(Elasticsearch + Logstash + Kibana)或 Loki 进行日志收集和分析。 - 监控与健康检查,结合 Prometheus + Grafana 监控 MySQL、Redis、Nginx 的健康状态,避免服务宕机。
- 结合 Kubernetes 进行集群部署,通过 K8s + Helm 实现容器编排,提供弹性扩展、自动恢复等能力,适用于大规模生产环境。
🚨 最后提醒:
上面的示例主要适用于本地和测试环境,生产环境请慎重使用!
在生产环境中,应根据实际需求优化 Dockerfile、镜像大小、安全策略、数据持久化方案、监控和运维策略,确保服务的稳定性和安全性。
在本地和测试环境,我们通常使用 Docker 搭建 MySQL、Redis、Elasticsearch,这非常方便,也能快速重置环境。但在生产环境中,使用 Docker 直接运行存储型服务可能存在数据丢失的风险,因为:
- 容器是无状态的,默认情况下,Docker 容器内的数据会随容器销毁而丢失。
-
数据存储依赖
volumes
或宿主机磁盘,但如果没有做好 数据持久化(如 RAID 备份、云盘挂载),当服务器宕机时,数据可能丢失或损坏。 - 数据一致性和高可用性问题,对于 MySQL 这类数据库,需要 主从复制 或 分布式存储,而 Docker 自建方案在生产环境下维护成本较高。
生产环境的推荐做法
✅ 使用云服务的数据库:如 AWS RDS、阿里云 RDS、腾讯云 MySQL 代替 Docker 运行的 MySQL。
✅ 使用云服务的 Redis/ES:如 阿里云 Redis、腾讯云 Redis、AWS ElastiCache,保证 高可用、自动备份和恢复。
✅ 使用 Kubernetes 挂载持久化存储:如果必须在 Kubernetes 集群内运行 MySQL、Redis、ES,可以使用 Ceph、NFS、EBS(AWS)、Alibaba Cloud Disk 持久化存储,避免数据丢失。
🚨 不建议直接用 Docker 运行 MySQL、Redis、ES 在生产环境,除非:
- 做好了数据备份和容灾措施(定期快照、主从同步)。
- 使用分布式数据库架构(如 MySQL 高可用集群)。
- Redis 只用作缓存,而不是数据存储(Redis 数据丢失影响较小)。