自建图床完全指南:飞牛NAS + EasyImage + Cloudflare Tunnel 公网访问方案

2026-06-19

写博客的人早晚会遇到这个问题:图片往哪放。

SM.MS 有 5MB 限制,Imgur 速度时好时坏,公共图床的数据不在自己手里。Cloudflare R2 免费额度不小但要绑信用卡。折腾了一圈后发现,最省心的方案还是在自己 NAS 上搭一个。

这篇文章记录我部署 EasyImage 图床的全过程——从 Docker 启动到安全加固,包括中间踩的几个坑。

架构

这套方案用三个组件搭起来:

  • EasyImage — 轻量 PHP 图床,无数据库,配置简单
  • Cloudflare Tunnel — 公网入口,不用暴露 NAS 端口
  • Caddy — 反向代理层,按域名把请求路由到对应服务

请求链路是:用户访问 → CF CDN → CF Tunnel → Caddy → EasyImage → 本地磁盘。

如果你不需要公网访问,只在局域网内用,可以省掉 CF Tunnel 和 Caddy 两层,直接访问 NAS 的 Docker 端口。

部署步骤

启动容器

EasyImage 的镜像很小,一条命令就能启动:

docker run -d \
  --name easyimage \
  --restart unless-stopped \
  -p 8093:80 \
  -v /path/to/config:/app/web/config \
  -v /path/to/images:/app/web/i \
  ddsderek/easyimage:latest

config 目录存 PHP 配置文件,i 目录存上传的图片。如果你习惯用 docker-compose,下面是一个参考配置:

version: '3'
services:
  easyimage:
    image: ddsderek/easyimage:latest
    restart: unless-stopped
    ports:
      - "8093:80"
    volumes:
      - ./config:/app/web/config
      - ./images:/app/web/i

启动后访问 http://你的IP:8093 进入安装页面。环境检测、填管理员账号、设置站点域名,三步完成。

配置公网路由

我用的是 Cloudflare Tunnel + Caddy 的组合。好处是无需在路由器上开端口,CF Tunnel 通过反向连接到 CF 边缘节点,家里网络环境再复杂也不影响。

在 Cloudflare DNS 面板加一条 CNAME 记录,指向你的根域名,代理模式打开(橙色云朵)。然后在 Caddyfile 里加一条路由:

img.yourdomain.com {
    reverse_proxy 192.168.1.100:8093
}

重载 Caddy 后通过 https://img.yourdomain.com 就能访问了。

如果你没用 Caddy,CF Tunnel 也支持直接在面板上配置转发规则,把 img.yourdomain.com 指向 http://localhost:8093,不需要额外反向代理层。

验证全链路

# 本地验证
curl http://localhost:8093
# 反向代理层验证
curl http://caddy-ip:8880 -H "Host: img.yourdomain.com"
# 公网验证
curl https://img.yourdomain.com
# 上传测试
curl -X POST -F "image=@test.png" https://img.yourdomain.com/api/index.php

踩坑记录

1. CF CDN 在国内被运营商干扰

移动网络下访问 CF 代理的域名,有时会返回 ERR_CONNECTION_RESET。这不是配置问题,是 CF 的部分 IP 段在国内被限制了。

我的处理方式:保持现状。因为图片嵌入到公众号文章后,读者通过微信服务器拉取图片,不受 CF 限制。自己平时上传走内网或者代理,体验流畅。如果你需要国内全运营商通畅,可以考虑在阿里云或其他国内服务器上做一层反向代理。

2. PHP opcache 缓存

EasyImage 用 PHP 的 opcache 缓存配置文件。如果你直接修改了 config.php 里的参数,页面不会立刻生效。

# 修改配置后必须重启容器
docker restart easyimage

这是一个容易忽略的点。修改配置后如果发现没变化,先重启容器试试。

3. 上传脚本的字段名

EasyImage 的 API 和网页上传入口用不同的字段名。API 用 image,网页用 file。如果用错了会报错。

curl -X POST \
  -F "image=@photo.png" \
  -F "token=你的Token" \
  https://img.yourdomain.com/api/index.php

安全加固(最重要的一节)

很多图床教程只管部署不管安全。EasyImage 安装完成后所有安全选项都是关闭的,需要手动加固。

问题在哪

EasyImage 有两个上传入口,认证机制完全独立:

  • 网页入口 (upload.php) — 靠登录 session 控制
  • API 入口 (api/index.php) — 靠 Token 校验

默认情况下两个入口都不设防。任何人只要知道你的地址就能匿名上传,公开广场还能遍历所有已上传的图片。

三层加固

# 1. 开启 API Token 认证
docker exec easyimage sed -i "s/'apiStatus' => 0/'apiStatus' => 1/" /app/web/config/config.php
# 去后台生成一个 Token,设置有效期

# 2. 开启登录上传
docker exec easyimage sed -i "s/'mustLogin' => 0/'mustLogin' => 1/" /app/web/config/config.php

# 3. 关闭公共广场
docker exec easyimage sed -i "s/'showSwitch' => 1/'showSwitch' => 0/" /app/web/config/config.php

# 4. 重启使配置生效
docker restart easyimage

加固完可以用同样的上传命令测试,不加 Token 应该返回 Token Error,匿名访问网页上传应该提示「本站已开启登陆上传」。直链图片仍然可以正常访问——这是图床的基本功能,不能因为加固就把已有图片的 URL 也禁了。

关键配置项一览:

'mustLogin'  => 1,   // 必须登录才能上传(网页入口)
'apiStatus'  => 1,   // 开启 API Token 校验(API 入口)
'showSwitch' => 0,   // 关闭公共图片广场
'maxSize'    => 10485760,  // 单文件最大 10MB

日常使用

配置一个简单的上传脚本会方便很多。把 Token 存到环境变量里,日常上传只需要一行:

./upload.sh screenshot.png

也可以集成到 PicGo 里,安装 picgo-plugin-easyimage 插件,配置 API 地址和 Token,截图后直接上传。

注意一定用 API 入口(/api/index.php),不是网页入口(/app/upload.php)。两者字段名不同,用错了会上传失败。

其他方案

如果你想试试别的:

  • Lsky Pro — 功能更丰富,支持多种存储策略,但要 MySQL,重一些
  • Chevereto — 专业图床,功能最全,免费版有限制
  • MinIO + 自研 — 适合需要 S3 兼容存储的场景,开发成本高

EasyImage 的优势是极轻量、无数据库依赖、配置直观。如果你只需要一个纯图片托管服务,它是性价比最高的选择。

总结

自建图床这件事,部署本身不难,三行命令的事。真正的坑在后头:默认配置把门全打开了,不作安全加固等于把 NAS 的存储空间向全世界开放。

动手之前先想清楚两件事:要不要公网访问(决定了路由方案),加固做到什么程度(决定了是否要 API Token + 登录双认证)。想清楚再动手,比部署完再返工快得多。