Laravel 项目 Docker 化部署与运维手册

本文档详细记录了如何将 “土豆食堂” (Lynx) 这个 Laravel 12 项目通过 Docker 进行容器化打包、部署、运行及日常维护。

个人项目,已开源:https://github.com/NightingaleWK/lynx

一、环境准备

在开始之前,请确保您的部署目标机器(例如 Windows 11)已安装并运行 Docker Desktop。

二、项目改造(首次部署需执行)

以下文件需要在您本地的项目代码中创建。

1. 目录结构

为了清晰地区分生产环境与 Sail 开发环境的配置,我们将所有生产部署相关的文件都存放在一个新的 docker-prod 目录中。

在项目根目录下,创建如下的目录和文件结构:

1
2
3
4
5
6
7
8
9
your-laravel-project/
├── docker-prod/ # <-- 新建,存放所有生产环境配置
│ ├── app/
│ │ └── Dockerfile
│ └── nginx/
│ └── default.conf
├── docker/# <-- 这是 Sail 的开发环境目录 (保留不变)
├── docker-compose.prod.yml# <-- 新建 (注意文件名)
└── .dockerignore# <-- 新建

2. 创建核心配置文件

请根据后续提供的文件内容,在您的项目中创建或替换以下文件。

  • docker-compose.prod.yml (服务编排文件)
  • docker-prod/app/Dockerfile (PHP 应用镜像构建文件)
  • docker-prod/nginx/default.conf (Nginx 配置文件)
  • .dockerignore (Docker 忽略文件)

3. 修改 Laravel 配置 (.env)

  1. 复制 .env.example 为 .env 文件。
  2. 关键修改:确保数据库和 Redis 的 HOST 指向 Docker Compose 中定义的服务名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
APP_ENV=production
APP_DEBUG=false
APP_KEY= # 留空,后续会通过命令生成

DB_CONNECTION=mysql
DB_HOST=mysql # <-- 必须是 mysql
DB_PORT=3306
DB_DATABASE=lynx# 您的数据库名
DB_USERNAME=sail# 您的用户名
DB_PASSWORD=password# 您的密码

CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=redis# <-- 必须是 redis
REDIS_PASSWORD=null
REDIS_PORT=6379

4. 生成 SSL 证书 (局域网 HTTPS)

  1. 在项目根目录下创建目录 docker-prod/certs。
  2. 打开终端 (如 Git Bash 或 WSL),进入项目根目录,执行以下命令:
1
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout docker-prod/ certs/lynx-key.pem -out docker-prod/certs/lynx.pem
  1. 重要提示:在执行过程中,当提示输入 Common Name 时,请输入您部署机器的局域网 IP 地址(例如 192.168.1.100)。
  2. 执行后,请检查 docker-prod/certs 目录下是否已生成 lynx.pem 和 lynx-key.pem 两个文件。

三、部署与初始化流程

步骤 1:构建并启动所有容器

在项目根目录下,打开终端,执行以下命令。此命令会根据 Dockerfile 构建应用镜像,并以后台模式启动所有服务。

1
2
3
# --no-cache 确保使用最新的基础镜像,避免缓存问题
# --build 强制重新构建镜像
docker-compose -f docker-compose.prod.yml up -d --build --no-cache

步骤 2:初始化 Laravel 应用

容器首次启动后,数据库是空的,应用也没有配置密钥。我们需要进入 app 容器执行一系列初始化命令。

  1. 生成应用密钥 (APP_KEY):
1
docker-compose -f docker-compose.prod.yml exec app php artisan key:generate
  1. 运行数据库迁移:
    此命令会根据 database/migrations 目录下的文件,在数据库中创建所有需要的数据表(包括 cache 表等)。\
1
docker-compose -f docker-compose.prod.yml exec app php artisan migrate
  1. 修正 storage 目录权限:
    这是解决 “Permission denied” 错误的关键步骤。此命令将容器内的 storage 和 bootstrap/cache 目录的所有权交给 php-fpm 的运行用户 www-data。
1
docker-compose -f docker-compose.prod.yml exec app chown -R www-data:www-data storage bootstrap/cache
  1. (可选)填充测试数据:
    如果您需要在测试时填充数据,可以运行此命令。请注意,生产环境不应执行此操作。
1
2
# --force 参数可以跳过生产环境的确认提示
docker-compose -f docker-compose.prod.yml exec app php artisan db:seed --force

注:若需填充数据,请确保 Dockerfile 中 composer install 命令没有使用 –no-dev 参数,以保证 Faker 等开发包被安装。

步骤 3:访问您的应用

完成以上步骤后,您的应用已经成功部署并运行。

  • 打开浏览器,访问 https://<您的部署机器 IP>。
  • 浏览器会提示证书不安全,请选择“高级” -> “继续前往”。
  • 您应该能看到 Laravel 的欢迎页面或您的应用首页。

四、日常运维命令

启动/停止服务

  • 启动所有服务 (后台运行):
1
docker-compose -f docker-compose.prod.yml up -d
  • 停止并移除所有容器:
1
docker-compose -f docker-compose.prod.yml down

此操作不会删除数据库和 Redis 的持久化数据。

查看状态与日志

  • 查看所有容器的运行状态:
1
docker-compose -f docker-compose.prod.yml ps
  • 实时查看所有服务的日志:
1
docker-compose -f docker-compose.prod.yml logs -f
  • 只看特定服务的日志 (例如 app 或 queue-worker):
1
2
docker-compose -f docker-compose.prod.yml logs -f app
docker-compose -f docker-compose.prod.yml logs -f queue-worker

代码更新流程

当您通过 git pull 更新了项目代码后:

  1. 重新构建镜像并重启服务:
1
docker-compose -f docker-compose.prod.yml up -d --build
  1. (如果需要)运行新的数据库迁移:
1
docker-compose -f docker-compose.prod.yml exec app php artisan migrate --force
  1. (推荐)清理并重建配置缓存:
1
docker-compose -f docker-compose.prod.yml exec app php artisan optimize:clear

执行 Artisan 命令

您可以在 app 容器内执行任何 Artisan 命令,语法如下:

1
docker-compose -f docker-compose.prod.yml exec app php artisan <您的命令>

五、完整配置文件代码

为了方便您快速部署,以下提供了文中提及的所有配置文件的完整代码。

1. docker-prod/app/Dockerfile

此文件定义了 PHP 应用容器的构建规则,基于 PHP 8.4 FPM Alpine 镜像,并安装了 Laravel 所需的所有扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 1. 基础镜像升级到 PHP 8.4
FROM php:8.4-fpm-alpine

# 设置工作目录
WORKDIR /var/www/html

# 2. 优化构建过程:合并依赖安装,并使用 virtual group 以便后续清理
RUN apk add --no-cache \
# 核心系统工具
zip \
unzip \
# PHP 扩展依赖
libzip-dev \
libpng-dev \
jpeg-dev \
freetype-dev \
icu-dev \
# 创建一个名为 .build-deps 的临时构建依赖组
&& apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
zlib-dev \
linux-headers

# 3. 借鉴 Sail,补全 Laravel 所需的核心扩展
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_mysql \
bcmath \
gd \
exif \
intl \
zip \
pcntl \
sockets \
&& pecl install redis \
&& docker-php-ext-enable redis

# 4. 清理工作:删除临时的构建依赖,减小镜像体积
RUN apk del .build-deps

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

# 拷贝 composer 文件并安装依赖
COPY composer.json composer.lock ./
# 生产环境优化:不安装开发依赖,并优化自动加载
# 注意:如果需要在容器内运行 seeder,请暂时移除 --no-dev 参数并重新构建
RUN composer install --no-interaction --no-plugins --no-scripts --prefer-dist --no-dev -o

# 拷贝项目代码
COPY . .

# 注意:权限问题将在首次启动后通过 exec 命令解决,以确保与宿主机挂载的目录权限正确
# RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

# 暴露 9000 端口
EXPOSE 9000

2. docker-prod/nginx/default.conf

此文件定义了 Nginx 的配置规则,实现了 HTTP 到 HTTPS 的自动重定向,并配置了 SSL 证书和 PHP-FPM 的转发规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server {
listen 80;
server_name _; # 匹配任何主机名
return 301 https://$host$request_uri; # 将所有 HTTP 请求重定向到 HTTPS
}

server {
listen 443 ssl;
server_name _;

# SSL 证书配置
ssl_certificate /etc/nginx/certs/lynx.pem;
ssl_certificate_key /etc/nginx/certs/lynx-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;

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

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

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# 注意这里的 fastcgi_pass 指向的是 app 服务的名称和端口
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}

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

3. docker-compose.prod.yml

此文件是 Docker Compose 的服务编排配置,定义了应用所需的所有服务:PHP 应用、Nginx、MySQL、Redis 和队列处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
services:
# 1. Laravel 应用服务 (PHP-FPM)
app:
build:
context: .
dockerfile: docker-prod/app/Dockerfile
container_name: lynx_app
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./:/var/www/html
# 使用匿名卷保护镜像中已构建的 vendor 和 node_modules 目录不被宿主机覆盖
- /var/www/html/vendor
- /var/www/html/node_modules
networks:
- lynx-network
# 确保 App 在数据库和 Redis 健康后才启动
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy

# 2. Nginx 服务
nginx:
image: nginx:alpine
container_name: lynx_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./:/var/www/html
- ./docker-prod/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./docker-prod/certs:/etc/nginx/certs
depends_on:
- app
networks:
- lynx-network

# 3. MySQL 数据库服务
mysql:
image: mysql:8.0
container_name: lynx_mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_USER: ${DB_USERNAME}
volumes:
- lynx_mysql_data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- lynx-network
# 从 Sail 借鉴的健康检查,确保数据库服务真正可用
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
retries: 3
timeout: 5s

# 4. Redis 服务
redis:
image: redis:alpine
container_name: lynx_redis
restart: unless-stopped
volumes:
- lynx_redis_data:/data
networks:
- lynx-network
# 从 Sail 借鉴的健康检查,确保 Redis 服务真正可用
healthcheck:
test: ["CMD", "redis-cli", "ping"]
retries: 3
timeout: 5s

# 5. Laravel Queue Worker 服务
queue-worker:
build:
context: .
dockerfile: docker-prod/app/Dockerfile
container_name: lynx_queue_worker
restart: unless-stopped
command: php artisan queue:work --verbose --tries=3 --timeout=90
working_dir: /var/www/html
volumes:
- ./:/var/www/html
- /var/www/html/vendor
- /var/www/html/node_modules
networks:
- lynx-network
# 确保 Worker 也在数据库和 Redis 健康后才启动
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy

# 定义网络
networks:
lynx-network:
driver: bridge

# 定义数据卷 (用于持久化)
volumes:
lynx_mysql_data:
driver: local
lynx_redis_data:
driver: local

4. .dockerignore

此文件用于指定在构建 Docker 镜像时需要忽略的文件和目录,可以显著减小镜像体积并提高构建速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.git
.github
.gitignore

.env
.env.example

/vendor/
/node_modules/

docker-compose.yml
/docker/

.idea
.vscode
*.DS_Store

六、总结

至此将 Laravel 项目通过 Docker 进行生产环境部署的完整流程已介绍完毕。这套方案具有以下特点:

  • 容器化:所有服务通过 Docker 隔离运行,环境一致性强
  • 易于部署:一键构建和启动,减少环境配置问题
  • 生产优化:包含 SSL 支持、健康检查、自动重启等生产级特性
  • 易于维护:提供完整的日常运维命令,代码更新流程清晰
  • 可扩展性:基于 Docker Compose,方便后续添加其他服务

如果在部署过程中遇到问题,请检查:

  1. Docker Desktop 是否正常运行
  2. 端口 80、443、3306 是否被占用
  3. .env 文件中的配置是否正确
  4. storage 目录权限是否正确设置