说明
php 有很多镜像, 但是本文不用任何构建好的 php 镜像, 而是从头自己制作. 无特殊需求, php 容器使用的基础镜像为 alpine , 如果有需要再更换为 ubuntu
alpine 版本
最终效果

目录结构
├── app
├── app_bak
│ └── index.php
├── docker
│ └── php-base
│ ├── Dockerfile
│ ├── entrypoint.sh
│ └── xdebug.ini
├── docker-compose.yml
├── nginx
│ ├── conf
│ │ └── nginx.conf
│ ├── html
│ │ └── index.html
│ └── log
│ ├── access.log
│ └── error.log
├── php
│ └── php-fpm.d
│ └── www.conf
└── tmp.txt
新建 docker 网络
bash
docker network create php-setup
nginx + php 环境搭建
nginx
新建目录
sh
mkdir -p nginx/conf
mkdir -p nginx/log
mkdir -p nginx/html
nginx 配置文件
nginx
#允许进程数量,建议设置为cpu核心数或者auto自动检测,注意Windows服务器上虽然可以启动多个processes,但是实际只会用其中一个
worker_processes auto;
events {
#单个进程最大连接数(最大连接数=连接数*进程数)
#根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。
worker_connections 1024;
}
http {
#文件扩展名与文件类型映射表(是conf目录下的一个文件)
include mime.types;
#默认文件类型,如果mime.types预先定义的类型没匹配上,默认使用二进制流的方式传输
default_type application/octet-stream;
#sendfile指令指定nginx是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。
# 如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度。
sendfile on;
#长连接超时时间,单位是秒
keepalive_timeout 65;
# =============== 日志配置 ===============
# 定义日志格式(可选,使用 main 格式)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 设置一个 default server 返回 444(拒绝)或 404,用于捕获非法请求
server {
listen 80 default_server;
server_name _;
return 444; # 关闭连接
}
server {
listen 80;
server_name www.kmphp84.com;
root /usr/share/nginx/html;
index index.html;
}
}
docker-compose.yml
yml
services:
php-setup-nginx:
image: nginx:1.18.0
container_name: php-setup-nginx
ports:
- "80:80"
volumes:
- ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/log:/var/log/nginx
- ./nginx/html:/usr/share/nginx/html
networks:
php-setup:
ipv4_address: 172.19.0.5
networks:
php-setup:
external: true
启动
laolang@ghost:php-setup$ docker compose up -d
[+] Running 1/1
✔ Container php-setup-nginx Started 0.3s
laolang@ghost:php-setup$
测试
- 在
nginx/html目录中添加一个index.html, 然后随便写点内容 - 本地添加域名映射

alpine
如何运行
首先运行一个 alpine 容器, 后续有需要运行的命令可以先在这个容器中尝试, 没问题了再复制到 Dockerfile
注意:
Alpine镜像本身只是一个极简操作系统,不包含任何长期运行的服务(如nginx、php-fpm等),所以直接 run 会立刻退出。alpine默认没有bash, 所以需要使用docker exec -it alpine /bin/sh进入容器
docker run -d --name alpine alpine:3.23 tail -f /dev/null
如何安装 php
第一步 替换源
清华源镜像参考 https://mirrors.tuna.tsinghua.edu.cn/help/alpine/
sh
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories
第二步 安装 php
alpine 使用 apk 安装软件
apk add --no-cache php84 php84-fpm php84-curl
有哪些需要关注的文件
如何查看安装了哪些依赖
/ # php --ini
Configuration File (php.ini) Path: /etc/php84
Loaded Configuration File: /etc/php84/php.ini
Scan for additional .ini files in: /etc/php84/conf.d
Additional .ini files parsed: /etc/php84/conf.d/00_curl.ini
/ #
php-fpm 的默认配置文件路径
直接启动 php-fpm84 , 然后查看进程就可以看到使用的是哪个配置文件
/ # php-fpm84
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 tail -f /dev/null
13 root 0:00 /bin/sh
26 root 0:00 {php-fpm84} php-fpm: master process (/etc/php84/php-fpm.conf)
27 nobody 0:00 {php-fpm84} php-fpm: pool www
28 nobody 0:00 {php-fpm84} php-fpm: pool www
29 root 0:00 ps aux
/ #
可以看到 include 的是 /etc/php84/php-fpm.d/www.conf
/etc/php84 # tail /etc/php84/php-fpm.conf
; used in logs and stats. There is no limitation on the number of pools which
; FPM can handle. Your system will tell you anyway :)
; Include one or more files. If glob(3) exists, it is used to include a bunch of
; files from a glob(3) pattern. This directive can be used everywhere in the
; file.
; Relative path can also be used. They will be prefixed by:
; - the global prefix if it's been set (-p argument)
; - /usr otherwise
include=/etc/php84/php-fpm.d/*.conf
/etc/php84 # ls -l /etc/php84/php-fpm.d/
total 24
-rw-r--r-- 1 root root 22133 Dec 20 12:36 www.conf
/etc/php84 #
将 /etc/php84/php-fpm.d/www.conf 复制到本地
docker cp alpine:/etc/php84/php-fpm.d/www.conf .
php
修改 www.conf
-
使用
www-data而不是nobody用户运行php-fpm -
php-fpm默认监听127.0.0.1, 当php和nginx不在一个容器时,nginx容器将无法连接到php容器user = www-data
group = www-datalisten = 0.0.0.0:9000
Dockerfile
docker
# 基础镜像使用 java8
FROM alpine:3.23
# 作者
LABEL maintainer="khl <xiaodaima2016@163.com>"
# 设置时区
ENV TZ=Asia/Shanghai
# 设置镜像源
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories
# 安装依赖
RUN apk add --no-cache \
php84 \
php84-fpm \
php84-curl
# 安全地创建 www-data 用户/组(如果尚未存在)
RUN if ! getent group www-data > /dev/null 2>&1; then \
addgroup -g 82 -S www-data; \
fi && \
if ! getent passwd www-data > /dev/null 2>&1; then \
adduser -u 82 -D -S -G www-data www-data; \
fi
# 复制入口脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 设置工作目录(应用代码将挂载到这里)
WORKDIR /var/www/html
# 启动脚本
ENTRYPOINT ["/entrypoint.sh"]
entrypoint
sh
#!/bin/sh
set -e
echo "🚀 Starting PHP-FPM 8.4..."
# 前台运行 PHP-FPM(关键!)
exec php-fpm84 --nodaemonize --fpm-config /etc/php84/php-fpm.d/www.conf
修改 nginx
注意:
- 修改了
root- 添加了
php文件处理
nginx
#允许进程数量,建议设置为cpu核心数或者auto自动检测,注意Windows服务器上虽然可以启动多个processes,但是实际只会用其中一个
worker_processes auto;
events {
#单个进程最大连接数(最大连接数=连接数*进程数)
#根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。
worker_connections 1024;
}
http {
#文件扩展名与文件类型映射表(是conf目录下的一个文件)
include mime.types;
#默认文件类型,如果mime.types预先定义的类型没匹配上,默认使用二进制流的方式传输
default_type application/octet-stream;
#sendfile指令指定nginx是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。
# 如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度。
sendfile on;
#长连接超时时间,单位是秒
keepalive_timeout 65;
# =============== 日志配置 ===============
# 定义日志格式(可选,使用 main 格式)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 设置一个 default server 返回 444(拒绝)或 404,用于捕获非法请求
server {
listen 80 default_server;
server_name _;
return 444; # 关闭连接
}
server {
listen 80;
server_name www.kmphp84.com;
root /var/www/html;
index index.php index.html;
# 处理 .php 文件
location ~ \.php$ {
fastcgi_pass php-setup-app:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
}
}
修改 docker-compose.yml
注意:
nginx容器添加了一个路径映射- 添加了
php容器
yml
services:
php-setup-nginx:
image: nginx:1.18.0
container_name: php-setup-nginx
ports:
- "80:80"
volumes:
- ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/log:/var/log/nginx
- ./nginx/html:/usr/share/nginx/html
- ./app:/var/www/html
networks:
php-setup:
ipv4_address: 172.19.0.5
php-setup-app:
image: khl/php-base:0.0.1
container_name: php-setup-app
volumes:
- ./app:/var/www/html
- ./php/php-fpm.d/www.conf:/etc/php84/php-fpm.d/www.conf
networks:
php-setup:
ipv4_address: 172.19.0.7
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
php-setup:
external: true
测试
php 文件内容如下
php
<?php
// 设置时区
date_default_timezone_set('Asia/Shanghai');
// 经典 Hello World
echo "Hello, World!" . "<br />";
// 输出 php 版本
echo "PHP version: " . PHP_VERSION . "<br />";
// 当前时间
echo "Current time: " . date('Y-m-d H:i:s') . "<br />";
?>
访问效果

laravel
生成 laravel 项目并复制到本地
在 alpine 容器中执行如下命令安装 laravel 需要的依赖
apk add --no-cache \
php84 \
php84-fpm \
composer \
php84-dom \
php84-fileinfo \
php84-iconv \
php84-mbstring \
php84-openssl \
php84-session \
php84-tokenizer \
php84-xml \
php84-xmlwriter \
php84-zip \
php84-phar \
php84-pdo \
php84-pdo_sqlite \
php84-xdebug
然后生成 laravel 项目
composer create-project laravel/laravel example-app "11.6.0"
将此 example-app 复制到本地并重命名为 app, 然后执行如下命令
find app -type d -exec chmod 755 {} \;
最终配置文件
php Dockerfile
docker
# 基础镜像使用 java8
FROM alpine:3.23
# 作者
LABEL maintainer="khl <xiaodaima2016@163.com>"
# 设置时区
ENV TZ=Asia/Shanghai
# 设置镜像源
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories
# 安装依赖
RUN apk add --no-cache \
php84 \
php84-fpm \
composer \
php84-dom \
php84-fileinfo \
php84-iconv \
php84-mbstring \
php84-openssl \
php84-session \
php84-tokenizer \
php84-xml \
php84-xmlwriter \
php84-zip \
php84-phar \
php84-pdo \
php84-pdo_sqlite \
php84-xdebug
# 安全地创建 www-data 用户/组(如果尚未存在)
RUN if ! getent group www-data > /dev/null 2>&1; then \
addgroup -g 82 -S www-data; \
fi && \
if ! getent passwd www-data > /dev/null 2>&1; then \
adduser -u 82 -D -S -G www-data www-data; \
fi
# 复制 xdebug 配置文件
COPY xdebug.ini /etc/php84/conf.d/99-xdebug.ini
# 复制入口脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 设置工作目录(应用代码将挂载到这里)
WORKDIR /var/www/html
# 启动脚本
ENTRYPOINT ["/entrypoint.sh"]
php-fpm.d/www.conf
关键配置, 完整文件可以自行复制
{.line-numbers}
user = www-data
group = www-data
listen = 0.0.0.0:9000
nginx
nginx
#允许进程数量,建议设置为cpu核心数或者auto自动检测,注意Windows服务器上虽然可以启动多个processes,但是实际只会用其中一个
worker_processes auto;
events {
#单个进程最大连接数(最大连接数=连接数*进程数)
#根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。
worker_connections 1024;
}
http {
#文件扩展名与文件类型映射表(是conf目录下的一个文件)
include mime.types;
#默认文件类型,如果mime.types预先定义的类型没匹配上,默认使用二进制流的方式传输
default_type application/octet-stream;
#sendfile指令指定nginx是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。
# 如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度。
sendfile on;
#长连接超时时间,单位是秒
keepalive_timeout 65;
# =============== 日志配置 ===============
# 定义日志格式(可选,使用 main 格式)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 设置一个 default server 返回 444(拒绝)或 404,用于捕获非法请求
server {
listen 80 default_server;
server_name _;
return 444; # 关闭连接
}
server {
listen 80;
server_name www.kmphp84.com;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# 处理 .php 文件
location ~ \.php$ {
fastcgi_pass php-setup-app:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
}
}
docker-compose.yml
yml
services:
php-setup-nginx:
image: nginx:1.18.0
container_name: php-setup-nginx
ports:
- "80:80"
volumes:
- ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/log:/var/log/nginx
- ./nginx/html:/usr/share/nginx/html
- ./app:/var/www/html
networks:
php-setup:
ipv4_address: 172.19.0.5
php-setup-app:
image: khl/php-base:0.0.1
container_name: php-setup-app
volumes:
- ./app:/var/www/html
- ./php/php-fpm.d/www.conf:/etc/php84/php-fpm.d/www.conf
networks:
php-setup:
ipv4_address: 172.19.0.7
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
php-setup:
external: true
laravel 修改配置
目录无权限
此时 laravel 应用已经可以访问, 但是在报错

最简单的办法就是直接修改本地目录的权限
chmod -R 777 storage bootstrap/cache
sqlite 连接报错
这是因为 laravel 默认使用 sqlite 做 session 存储, 修改为 file 即可
laravel 项目跟目录的 .env 修改如下内容即可
SESSION_DRIVER=file
debug
laravel 项目跟目录添加 .vscode/launch.json
json
{
"version": "0.2.0",
"configurations": [
{
"name": "xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
]
}
然后在 routes/web.php 中打个断点, 访问应用首页即可
