使用 -p 127.0.0.1:8080:80 可限制 FrankenPHP 容器仅本机访问,Docker 网络代理层拦截外部连接,无需修改 Caddy 配置;docker-compose 中须写完整 "127.0.0.1:8080:80" 格式;云服务器还需关闭安全组公网 80/443 放行。

docker run 时用 -p 127.0.0.1:8080:80 只绑定本地回环
FrankenPHP 容器默认监听 0.0.0.0:80(即所有接口),但你完全可以通过 Docker 的端口映射语法限制只对内网或本机暴露。
最直接的做法是显式指定宿主机监听地址为 127.0.0.1:
-
docker run -p 127.0.0.1:8080:80 dunglas/frankenphp→ 宿主机仅localhost:8080可访问,其他机器连不上 -
docker run -p 192.168.1.100:80:80 dunglas/frankenphp→ 仅该内网 IP 所在机器可被其他局域网设备访问(需确保防火墙放行) - 不写地址前缀(如
-p 80:80)等价于-p 0.0.0.0:80:80,这是默认行为,也是最危险的——相当于全网可连
注意:127.0.0.1 绑定后,容器内服务仍照常运行,只是宿主机网络栈拒绝了来自外部 IP 的连接请求。这层隔离发生在 Docker daemon 网络代理层,无需改 Caddy 配置或 SERVER_NAME。
docker-compose 中限制 bind_ip 需写 host:port 格式
Docker Compose 不支持直接写 127.0.0.1:80 这种简写,必须用完整 host:container_port 结构,否则会被当作 0.0.0.0 处理。
立即学习“PHP免费学习笔记(深入)”;
正确写法示例(docker-compose.yml):
services:
php:
image: dunglas/frankenphp
ports:
- "127.0.0.1:8080:80"
- "192.168.1.100:80:80"
- 第一行:仅本机可通过
http://localhost:8080访问 - 第二行:局域网内其他机器可通过
http://192.168.1.100直接访问(前提是宿主机防火墙允许80/tcp) - 若同时写两行,它们互不干扰;但不要写成
- "127.0.0.1:80:80"+- "80:80",后者会覆盖前者并重新打开公网入口
容器内监听地址不用动,别碰 SERVER_NAME=:80
有人误以为要配 SERVER_NAME=:80 才能“只发给内网”,其实这是两回事:
-
SERVER_NAME=:80是告诉 Caddy:别管域名、别申请证书、纯 HTTP 监听:80—— 它影响的是容器内服务行为,和端口对外可见性无关 - 即使你设了
SERVER_NAME=example.com,只要-p绑定在127.0.0.1,外网照样连不到,ACME 挑战也根本不会触发(因为 Let's Encrypt 根本访问不了你的 80 端口) - 反过来,如果你写了
-p 0.0.0.0:80:80却又设SERVER_NAME=:80,那等于把 HTTP 服务裸奔在公网上——非常危险
结论:控制“谁可以连进来”,只靠 -p 的绑定地址;SERVER_NAME 只管“容器里 Caddy 怎么处理请求”。两者职责分明,混用容易翻车。
云服务器上还要关掉安全组里的 80/443 公网放行
就算你在 Docker 层绑定了 127.0.0.1,阿里云/腾讯云的安全组如果还开着 0.0.0.0/0:80,外部请求仍可能穿透到宿主机 iptables,再被 Docker 转发(取决于内核规则优先级)。
- 务必登录云控制台,在安全组规则中删除或禁用面向公网的
80和443端口放行 - 只保留必要管理端口,比如
22(SSH)限制源 IP,8080(调试用)也建议加白名单 - 验证方式:从另一台公网机器执行
curl -v http://你的公网IP:80,应返回 connection refused 或超时,而不是 FrankenPHP 响应
真正只向内网发布,是三层控制:Docker 端口绑定 → 宿主机防火墙(ufw/iptables)→ 云平台安全组。少一层,就多一分暴露风险。



















