开头:直接进入主题,不引入模板。
1. 为什么我坚持用 Docker 加 phpMyAdmin,而不是直接在系统里装软件
如果你稍微有几年玩服务器的经验,应该都有过这样的场景:接手一台 Linux 机器,里面有 MySQL,业务跑得好好的,但你想看某个表里的数据,没有现成的图形客户端,只能用命令行敲SELECT * FROM。列一多,行一挤,眼睛直接看花。后来大家开始用 phpMyAdmin、Adminer 这类网页管理工具,但真要在服务器上装一套 phpMyAdmin,往往得牵扯 PHP 环境、nginx/apache 配置、文件权限、session 问题,折腾一晚上不算稀奇。
用 Docker 跑 phpMyAdmin 就完全是另一种体验。镜像拉下来,容器一启,浏览器开个 8000 端口就是一套完整的图形化管理界面,MySQL 照样可以跑在另一个容器里或者宿主机上。你不用它的时候,一条docker stop就能让环境干干净净地停掉,不用卸载 PHP、不用删 nginx、不用担心留下一堆残留文件。这篇我打算把我自己多次部署 phpMyAdmin 和 MySQL 容器组合的完整经验拆开写清楚,环境怎么规划、命令怎么执行、Compose 怎么编排,再到部署完以后哪些坑我会建议你提前填上,一步步说透。
1.1 phpMyAdmin 并不复杂,但系统环境会把人逼疯
单看 phpMyAdmin 本身,它就是一个 PHP 写的 Web 应用,逻辑不复杂。为啥我非要容器化?是因为它运行依赖的 PHP 版本和扩展实在太容易跟同一台机器上的其他业务打架。你机器上可能跑着一个基于 PHP 7.4 的老 CRM,然后 phpMyAdmin 的新版本要求 PHP 8.1;你想装两个并存的 PHP 环境,又开始跟 fpm、nginx 的 fastcgi 配置纠缠。你只是需要一个数据库管理界面,没必要为一个工具把系统环境搞成一锅粥。
Docker 的隔离思想正好解决这个问题:phpMyAdmin 镜像内部自带匹配的 PHP 运行环境,跟宿主机完全隔离。docker pull phpmyadmin之后,我只管绑定端口、给环境变量,应用和依赖都封装在一个可重复的镜像里。复制到另一台机器也一样启动,不需要在每台服务器上重复完成 apt/yum install、改 php.ini、开 extension 这些机械动作。
1.2 一次编排,换机器不翻车的复现能力
还有一点是"可复现"。以前我部署了一台服务器上的 phpMyAdmin,过了半年需要在新服务器复用同样的配置,还得回忆当时改了哪些文件、装了什么版本。但用 Docker 之后,我把启动命令或 docker-compose.yml 一存档,迁移就是复制粘贴一条命令的事。镜像默认锁定了 phpMyAdmin 版本、PHP 版本和系统依赖;MySQL 容器的版本、字符集、存储引擎也通过镜像和环境变量固定下来。只要镜像仓库里还留存这些 tag,我就能在任何时间把环境恢复成当时的状态。
所以与其把 Docker 部署 phpMyAdmin 当作花哨新玩法,不如把它看作标准操作流程:环境一致性更强、出错概率更低、排障路径更清晰。
2. 部署前先想清楚:容器之间是怎么"互相找到"的
很多第一次用 Docker 部署 MySQL + phpMyAdmin 的人都会卡在同一句话上:"我已经把 phpMyAdmin 跑起来了,为什么输入 localhost 连不上数据库?"根子在于没有理解容器网络。你容器里的localhost只代表容器自己,不代表服务器。MySQL 跑在一个容器里,phpMyAdmin 跑在另一个容器里,两者需要通过网络通信,而最常见的正确姿势是:让它们在同一个自定义 bridge 网络里,用服务名互相访问。
2.1 容器通信的两种思路,别再用 --link 了
早期教程经常会教你用--link mysql-container:db来让两个容器通信。这个参数在老版本 Docker 里能用,但官方早就标记为遗留功能,未来的 Docker 版本很可能直接移除。而且--link是单向依赖,不适合多个服务互相访问的场景。现在推荐的方案是自定义 bridge 网络。
自定义 bridge 网络有几个实际好处:第一,同一个网络里的容器可以直接通过容器名当主机名互 ping,比如从 phpMyAdmin 容器里访问mysql-server:3306,不需要关心 MySQL 容器的 IP 地址变没变;第二,网络内部默认隔离,容器间可以通过加入或退出网络来控制谁跟谁通信;第三,你还可以用 User-defined bridge 内置的 DNS 解析给容器做服务发现,Compose 编排时的服务名本质上也是这么工作。
具体创建一条自定义网络的命令是:
docker network create mysql-pma-net这条网络一旦建好,之后所有要互通的容器都通过docker run时加--network mysql-pma-net挂进去。MySQL 容器和 phpMyAdmin 容器放同一个网络,彼此就能用对方--name指定的名字找到对方了。
2.2 端口、镜像 tag 与 MySQL 版本要提前对齐
动手之前,另一个重要决策是镜像版本。MySQL 推荐用mysql:8.0而不是默认的mysql:latest。latest一旦遇到大版本升级,很可能把存储结构、认证方式全变了,你拉下来启动、然后旧数据起不来才头疼。锁定一个小版本可以在可控范围内更新,比如mysql:8.0.39,保守一点选mysql:8.0也够用。phpMyAdmin 的镜像尽量也用具体 tag,比如phpmyadmin:5.2.2或phpmyadmin:5.2.1;这个镜像本身是基于 Apache + PHP 的官方构建,启动方式比从源码包配置软链到 web 目录要省心得多。
端口怎么规划也值得提前想:phpMyAdmin 默认监听 80 端口,如果你是直接容器启动,通常映射成一个高位端口,比如-p 127.0.0.1:8088:80;后半段的80是容器内部端口,不可改,前半段的8088是宿主机端口,可以随便选不冲突的。MySQL 容器大多数情况下不需要把 3306 端口映射到宿主机,因为只有 phpMyAdmin 和同网络内的后端服务要用它。如果你还是需要本机连接 MySQL,映射成-p 127.0.0.1:3307:3306也能避免和宿主机的 mysql 冲突。
3. 最小可用部署:两条 docker run 把 phpMyAdmin 和 MySQL 跑起来
我按照最终实际使用的方案来讲,先不引入 Compose,因为 Compose 只是把这些命令做了一个"清单化",如果你能理解下面的参数,Compose 里的每一行都会变得很好懂。为了方便新手照抄,我的环境假设是一台 Linux 服务器或者本机已经装了 Docker 的 Windows/Mac 电脑,MySQL 数据卷挂载在宿主机目录~/docker_data/mysql下。
3.1 启动一个带数据卷的 MySQL 容器
先拉取镜像并创建容器。MySQL 容器必须重点关注的三个配置是:root 初始密码、数据目录持久化、字符集与排序规则。字符这点很容易被忽略:如果你接手的项目表结构默认是 utf8mb4,但容器初始化的系统表是 latin1,后续通过 phpMyAdmin 导数据就可能看到乱码或者字符集报错。
docker pull mysql:8.0docker run -d \ --name mysql-server \ --network mysql-pma-net \ -e MYSQL_ROOT_PASSWORD=ChangeMe2024 \ -e MYSQL_DATABASE=testdb \ -e TZ=Asia/Shanghai \ -v ~/docker_data/mysql:/var/lib/mysql \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci解释几个参数:
-d表示后台运行。--name mysql-server是容器名,也是整个 bridge 网络里的主机名。-e MYSQL_ROOT_PASSWORD=ChangeMe2024只会在数据卷为空时生效,第一次初始化 root 密码用。数据卷创建完成后,即使你改了这个环境变量,root 密码也不会变,这一点很容易让习惯改命令重启的人误解。-v ~/docker_data/mysql:/var/lib/mysql把 MySQL 的数据目录挂载到宿主机,容器删除后数据还在。--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci是我建议加上 MySQL 服务端参数,避免后面出现字符集不一致的问题。
3.2 启动 phpMyAdmin 容器并传入数据库地址
等 MySQL 容器处于 healthy 状态(没有 healthcheck 的话就直接等两秒),再启动 phpMyAdmin:
docker pull phpmyadmin:5.2.2docker run -d \ --name phpmyadmin \ --network mysql-pma-net \ -e PMA_HOST=mysql-server \ -e PMA_PORT=3306 \ -e UPLOAD_LIMIT=300M \ -p 8088:80 \ phpmyadmin:5.2.2这里有一个关键点:环境变量PMA_HOST不是填 localhost,也不是填服务器公网 IP,而是填 MySQL 容器的名字mysql-server。只有两个容器同在一个自定义网络里,phpMyAdmin 才能通过 Docker 内置 DNS 把这个名字解析到 MySQL 容器的 IP。PMA_PORT默认就是 3306,写上是为了让配置可视。UPLOAD_LIMIT可以控制导入 SQL 文件的最大限制,因为默认 phpMyAdmin 上传限制往往只有 2M,写 300M 会让我们后面演示大 SQL 导入时更从容。
启动完后看容器状态:
docker ps如果mysql-server和phpmyadmin都显示了,且 STATUS 不是 "restarting" 或 "Exited",就可以用浏览器访问http://服务器IP:8088(本机部署则访问http://127.0.0.1:8088)。
3.3 第一次登录,你大概率会碰到哪两个问题
首次打开 phpMyAdmin 登录界面,输入root和刚才的MYSQL_ROOT_PASSWORD,如果一切正常,你就会进入到 MySQL 的图形化管理主界面。如果没有,我很确定你会遇到下面两种情况之一。
第一种:浏览器能打开 phpMyAdmin,但登录时提示mysqli::real_connect(): (HY000/2002): Connection refused。这说明 phpMyAdmin 容器无法连接到 MySQL 容器。先确认一下你的docker run命令里两个容器是否都带了同一个--network mysql-pma-net,别一个用了默认桥接网络,另一个用了自定义网络。检查命令是:
docker inspect mysql-server -f "{{json .NetworkSettings.Networks}}"输出里应该有mysql-pma-net字段,如果没有,需要用docker network connect mysql-pma-net mysql-server把这个容器追加进网络,然后重启 phpMyAdmin。
第二种:能连上 TCP,但提示Access denied for user 'root'@'172.x.x.x'。这说明 MySQL 用户权限里没有允许来自这个来源 IP 的 root 登录。MySQL 8.0 初始化 root 默认一般只允许 localhost,虽然你通过MYSQL_ROOT_PASSWORD指定了密码,但它绑定的 host 是 localhost,而来自 phpMyAdmin 容器的连接 IP 显然不是 localhost。这个问题也有解,我放在第 5 章讲,因为牵涉到安全配置,不要为了省事直接去 MySQL 里把所有 root host 改成%。
4. 图形化界面到底能干哪些事:浏览表、执行 SQL、管权限与导入导出
phpMyAdmin 很多年来都是最老牌的 Web 数据库管理工具。登录进去以后,左侧栏是所有数据库和表的树形列表,右侧是欢迎界面和数据库统计。管理一个中小型项目的数据,我日常高频用的是下面这几类功能,也是我会推荐一个 DBA 或后端同学熟悉的几个入口。
4.1 浏览结构和执行 SQL:可视化操作背后的管理逻辑
第一个入口是左侧的数据库名,点开以后能看到库里所有的表,每张表的行数、大小、整理规则一目了然。点某张表名,默认进入"浏览"标签页,以分页形式展示表里所有数据。这里我经常嫌它默认只显示 30 行,所以在页面下方把显示行数改成 200,或者直接点击"SQL"标签,输入自己的查询:
SELECT id, user_name, created_at FROM users ORDER BY id DESC LIMIT 50;在 phpMyAdmin 里执行 SQL 时,它也支持多语句执行。从 Navicat 或 Sequel Pro 这类客户端迁移过来的人,最容易不习惯的一点是:SQL 编辑器里如果选择了多条 UPDATE/DELETE,一次执行会有安全提示。这是故意设计的,防止你误操作把整张表的数据改没了,不是故障。我的建议是不需要刻意关闭这个提示,生产环境往往需要的正是二次确认。
权限管理同样在这里集中操作。点击顶部"账户"标签,可以查看 MySQL 里所有用户,也可以创建新用户,指定它能访问哪些数据库、拥有哪些权限。比如给某个应用单独建一个app_user,只授权app_db.*的 SELECT/INSERT/UPDATE/DELETE,而不用把 root 给出去。
4.2 导入导出最容易踩的字符集与超时坑
phpMyAdmin 做数据迁移非常方便,但一定要记住:导入导出不是简单地"下载一个文件,再上传上去"。导出时,在"导出"标签页里选择"自定义"导出方式,我总会做三个设置:
- 格式选 SQL。
- 在"格式特定选项"里勾选
添加 DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER 语句,这样在目标库重新导入时不会因为残留表报错。 - 重点检查"表名创造语句的生成方式"改成
CREATE TABLE,这是默认,不用动;但"使用语句创建数据库"我一般不勾选,因为导入文件时我会先建好库并选中它。
导入时,最大的坑往往不是 SQL 语法错误,而是max_allowed_packet和 phpMyAdmin 上传限制。我遇到过一次导入一个几十 MB 的 SQL 文件,结果 phpMyAdmin 整个页面转了几分钟然后超时。后来我在启动 phpMyAdmin 时加了-e UPLOAD_LIMIT=300M,同时在 MySQL 容器的启动参数中增加 MySQL 服务端的限制调整入口。不过最稳妥的处理方法是:把大 SQL 文件直接复制到 phpMyAdmin 容器里的/tmp下,然后在 SQL 页面用SOURCE命令执行,避免 Web 上传超时。这个办法是我在几十 MB 数据恢复时最常用的方案。
5. 别让 phpMyAdmin 变成你服务器的后门:安全收紧要早于业务上线
phpMyAdmin 这类 Web 管理工具,便利性有多高,风险就有多大。一个暴露在公网、默认 admin 口令、还开着 root 登录的 phpMyAdmin,基本等于把你的数据库密码拱手送给扫描器。所以你把服务跑起来的那一刻,就应该同步把安全措施一起做掉。下面这几点没有一个是高深技巧,但它们能挡掉绝大多数隐患。
5.1 绑定 127.0.0.1,而不是 0.0.0.0
我最推荐的做法是:如果这台 MySQL 只有你自己管理,phpMyAdmin 的端口不要暴露到公网,而是只绑定在127.0.0.1主机的回环地址上。前面 docker run 命令我把端口映射写成了:
-p 127.0.0.1:8088:80而不是-p 8088:80。两者差别非常大:前者只有宿主机自己可以访问127.0.0.1:8088,外部网络即使知道 IP 也根本无法建立 TCP 连接;后者则会让 Docker 默认把端口绑到所有网卡上,公网 IP 的 8088 口直接开放。如果你人在服务器上办公,仅仅操作本机端口就够用了;如果你从自己的电脑上需要访问,也应该通过 SSH 隧道转发,而不是开放公网端口。
另一个做法是部署一套带反向代理的网关,用 nginx 或 Caddy 把https://db.example.com转发到本机的 8088 端口,再由反向代理负责 TLS 和 Basic Auth。这样至少不用暴露裸的 phpMyAdmin 页面。Caddy 的好处是自动续 HTTPS 证书;nginx 则更通用,但配置起来需要多写几行。记住核心原则:没有任何理由把 phpMyAdmin 直接用公网 HTTP 端口暴露出去。
5.2 MySQL 的 root 账号别用来登 Web 界面
根因是 MySQL 8.0 默认的 root 用户通常只允许从localhost连接。从 phpMyAdmin 容器连过来,源 IP 是容器网段里的地址,大多数情况下 root 会登录失败。我不建议为解决这个问题去执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'NewPassword'; UPDATE mysql.user SET host = '%' WHERE user = 'root';因为把 root 的允许来源改成%等于放弃了 MySQL 最基础的来源限制。正确做法是创建一个只用于 Web 管理的专用账号,授权给需要的库,并限定来源网络范围。你先通过能连上 MySQL 的命令行或进入 mysql-server 容器执行 MySQL 客户端,创建一个用户:
CREATE USER 'pma_user'@'172.20.0.%' IDENTIFIED BY 'PmaUserPass2024'; GRANT ALL PRIVILEGES ON `app_db`.* TO 'pma_user'@'172.20.0.%'; FLUSH PRIVILEGES;172.20.0.%是自定义 bridge 网络通常使用的网段,具体要看docker network inspect mysql-pma-net输出里的 Subnet。你甚至可以直接写'pma_user'@'%',然后只授予某个数据库权限,风险也会比 root 小。接着启动 phpMyAdmin 时,用环境变量传入专用用户:
-e PMA_USER=pma_user \ -e PMA_PASSWORD=PmaUserPass2024 \phpMyAdmin 支持直接在登录页预填用户名和密码,但我从不在环境变量中写明文生产密码。原因很简单:docker inspect phpmyadmin或服务器上能看到进程环境变量的人都能拿到密码。实际使用中,我会在首次启动时只设置PMA_HOST,然后每次登录手动输入账号密码。如果真要在环境变量里配,那就要在安全策略上确认这台宿主机只有可信用户能登录。
5.3 镜像和容器运行时间的红线
phpMyAdmin 这种管理工具一旦有漏洞,影响面是数据库本身。所以每过几个月,我都会主动执行:
docker pull phpmyadmin:5.2.2 docker stop phpmyadmin && docker rm phpmyadmin再原来类似的参数启动一个新容器。数据库没必要频繁升级,但 Web 管理工具值得跟上补丁。同样地,MySQL 容器升级前一定、一定先备份数据卷,不要直接docker exec乱改系统库;只需要把旧容器停止并保留数据卷,用新镜像起一个新容器指向同一卷,MySQL 会自动做系统表升级,但升级之后想回滚就非常困难,必须有备份兜底。
6. 想省心就上 docker compose:一套配置管好两个容器
到这一步,你已经可以用手敲docker run把 phpMyAdmin 和 MySQL 管理起来了。但如果这个项目要在新同事电脑上复现、或者要部署到测试服务器,你不可能让每台机器都去手输两条长命令。docker-compose.yml或者现在 Docker 插件化的docker compose子命令,能把这些部署逻辑固化成代码。这也符合"基础设施即代码"的思路。
6.1 写一份多服务编排文件的编排逻辑
在项目目录下建一个文件夹,比如mysql-pma-stack/,里面创建docker-compose.yml:
services: mysql-server: image: mysql:8.0 container_name: mysql-server restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-ChangeMe2024} MYSQL_DATABASE: app_db TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci volumes: - mysql_data:/var/lib/mysql networks: - db-net phpmyadmin: image: phpmyadmin:5.2.2 container_name: phpmyadmin restart: unless-stopped depends_on: - mysql-server environment: PMA_HOST: mysql-server PMA_PORT: 3306 UPLOAD_LIMIT: 300M ports: - "127.0.0.1:8088:80" networks: - db-net volumes: mysql_data: networks: db-net:注意一份 Compose 文件里的services下的服务名,在同一个默认网络上可以直接互相解析。比如 phpMyAdmin 服务访问mysql-server,等价于前面自定义网络的容器名。depends_on表示 phpMyAdmin 会在 mysql-server 容器启动后再启动,但 MySQL 从启动到真正能接受连接还有一小段初始化时间,所以就算有 depends_on,phpMyAdmin 第一次启动也可能会碰到连接拒绝。解决方式是等几秒刷新页面即可,或者给 mysql-server 加healthcheck,让 phpMyAdmin 真正等数据库 ready 再启动。
Healthcheck 的详细配置贴在下面,作为进阶选项:
services: mysql-server: image: mysql:8.0 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p$MYSQL_ROOT_PASSWORD"] interval: 5s timeout: 3s retries: 206.2 用 compose 管理生命周期的常用命令集
使用 Compose 部署时要记住的核心命令总共就几条:
docker compose up -d这条命令会读取当前目录下docker-compose.yml或compose.yaml,创建网络、数据卷并启动服务。查看服务状态:
docker compose ps看日志:
docker compose logs -f phpmyadmin把整个服务栈停掉并删除容器(数据卷默认不删,放心):
docker compose down如果还想把数据卷也一起删掉,才需要加-v参数,我建议在生产环境和有重要数据的环境里永远不要加-v,因为你数据卷没删还能从旧卷挽回,加了就什么都没了。
Compose 最强的点是环境一致性:我把这套docker-compose.yml加上一个.env.example文件扔到 Git 仓库里,任何人拿到后只需复制成.env、填好 MYSQL_ROOT_PASSWORD,就可以在本地还原出一套可运行的 MySQL + phpMyAdmin 环境。这也是我强烈建议团队里数据库管理环境统一走这条路的根本原因。
7. 遇到连接失败、访问不了,按这条链路排查就够了
说实话,部署 Docker 里的 phpMyAdmin 极少是一次就成功的。哪怕是老手,也会因为网络没配对、端口写错、镜像启动失败而折腾一段时间。下面的排查思路是我自己用过的,能覆盖 90% 的情况,建议你按顺序一个个验证。
7.1 第一梯队:容器本身是不是健康运行
先在宿主机上执行:
docker ps -a看STATUS这一列。如果 phpMyAdmin 容器显示 Exited,或者 STATUS 里反复出现 "Restarting",则多半是启动时参数有误。立刻看日志:
docker logs phpmyadminphpMyAdmin 镜像的日志会明确告诉你它尝试连接 MySQL 时发生了什么。比如日志里可能写着Invalid value for PMA_HOST,说明环境变量没传对;也可能写着The mysqli extension is missing,不过官方镜像一般情况下不会缺扩展,如果真缺,多半是你改了一个不存在的 tag 或者用了别人的非官方镜像。
MySQL 容器不健康同理:
docker logs mysql-server | tail -n 50重点看有没有[ERROR] [MY-010584] No server UUID generated或者Can't start server: Bind on TCP/IP port这类启动错误。后者通常意味着宿主机 3306 端口已经被占用,但你并没有映射3306到宿主机,就不会出现这问题;如果确实映射了,需要换个宿主端口如 3307。
7.2 第二梯队:网络连通性和 DNS 解析
如果两个容器都在运行,但是 phpMyAdmin 登录时还提示 Connection refused,就要检查 phpMyAdmin 容器到底能不能解析到 mysql-server。用两条命令验证:
docker exec -it phpmyadmin sh -c "getent hosts mysql-server"有输出说明 DNS 解析正常;没有输出但你又确认两个容器处于同一个 network,就要检查 mysql-server 容器名是不是真叫 mysql-server,docker ps会显示,或者用docker inspect确认。
进一步测试端口连通性:
docker exec -it phpmyadmin sh -c "nc -zv mysql-server 3306"镜像里不一定有 nc,你也可以临时进入容器后用 PHP 做 socket 测试,但更简单的方法是看docker network inspect mysql-pma-net,查看网关和两个容器的 IP,然后从 phpMyAdmin 容器尝试 ping 一下 MySQL 容器 IP。
7.3 第三梯队:MySQL 用户权限与登录认证方式
网络没问题、端口也通,那就只剩用户和密码的问题。常见的报错有两种:
Access denied for user 'root'@'localhost':虽然网络是通的,但 MySQL 内部认为 root 的来源是 localhost,而容器连接被视作来自非本机地址,所以拒绝。处理方式参见第 5 章,创建专门账号。Authentication plugin 'caching_sha2_password' cannot be loaded:低版本客户端连高版本 MySQL 时的经典问题。phpMyAdmin 5.2 镜像内置的客户端是支持 caching_sha2_password 的,所以不会出现;但如果你连接的是 MySQL 5.7 或老实例,就要考虑认证插件匹配。
还有种情况是密码确实错了。可以用命令行先进 mysql-server 容器内部验证密码对不对:
docker exec -it mysql-server mysql -uroot -p能进说明密码正确;不能进说明初始化时的 MYSQL_ROOT_PASSWORD 没生效,或者数据卷在第一次初始化时没有使用这个环境变量。遇到这种问题,想靠改环境变量重启恢复密码是没用的,因为密码已经写到数据卷里的系统表了,你需要用--skip-grant-tables这类维护模式去重置。所以初始化时一定要想好密码,不要随便设个临时密码。
7.4 宿主机访问不通时,从浏览器这一侧逆推
如果你已经能在服务器上用curl http://127.0.0.1:8088拿到页面,但浏览器访问公网 IP 打不开,那问题大概率不在 Docker,而在防火墙或安全组策略。Linux 上可以检查firewalld或ufw是否放行了宿主机该端口;云服务器上检查控制台的安全组入方向规则。这也再次说明了第 5 章那条建议的合理性:既然你决定 phpMyAdmin 只绑定 127.0.0.1 给反向代理访问,那你的安全组根本不需要放行 8088 端口,只需要放行 80/443 端口给反向代理即可。少开一个端口,就少一个攻击面。
如果是 Windows/Mac 上的 Docker Desktop 用户,端口映射一般不会有防火墙拦你,但值得注意:-p 127.0.0.1:8088:80在 Docker Desktop 里访问没问题,却只能从当前机器访问;如果你想让同局域网的其他设备访问,需要改成-p 8088:80,并同时确认 Docker Desktop 的防火墙策略允许。本地开发用绑定回环比较好,团队协作时则建议走网关。
写在这一轮部署之后的一点个人习惯
这套方法我已经在各种环境下部署过很多次。个人体会很明确:与其把 phpMyAdmin 和 MySQL 分别裸装在主机里,不如让两个容器共享同一个自定义网络,再把管理端口绑在回环地址上,用 Compose 文件管住生命周期。这样我换一台新机器,从拉取代码到把整套环境恢复出来,通常五分钟内能搞定,而且不用担心操作系统的 PHP 版本、库目录结构或者数据残留。
还有一个我后来才养成的小习惯:每次登录完 phpMyAdmin,我都会把临时使用的账号密码在密码管理器里记录,而不是直接写在.env文件里;.env文件里只放一个明文示例密码并提交到 git 前,我会故意改成不会在真实环境生效的假值。MySQL 数据卷备份则直接打包宿主机上的~/docker_data/mysql目录,恢复的时候把备份解压回同一个路径再启动容器。这个流程谈不上优雅,但胜在可靠,真出问题时不会让你手忙脚乱。希望这份从网络规划到排障链路的经验,能让你少走几次弯路。