搭建个人博客之后,我很快遇到一个很现实的问题:文章里的图片到底应该放在哪里?
一开始最简单的做法当然是直接上传到博客后台,比如 Halo 本身也支持文章图片、附件、封面图这些能力。但随着文章越来越多,截图、封面、教程配图也会越来越多。如果所有图片都堆在博客服务器里,后面会带来几个问题:服务器磁盘占用越来越高,迁移博客时图片也要一起搬,文章里的图片链接还可能因为路径变化失效。
所以我最后选择把图片从博客系统里拆出来,单独做一个图床服务。目前我用的是开源项目CloudFlare-ImgBed,它的项目文档
CloudFlare-ImgBed 是什么
管理端页面

CloudFlare-ImgBed 是一个开源文件托管项目。虽然名字里带着 ImgBed,但它不只是一个简单的图片上传页面,更像是一个轻量的个人文件托管系统。
它支持 Docker 部署,也支持 Cloudflare Pages / Workers 这类无服务器部署方式。存储后端也比较灵活,可以接入 Telegram、Discord、Cloudflare R2、S3、HuggingFace、WebDAV 等不同渠道。
对个人博客来说,它最直接的作用就是:把博客文章里的图片独立托管出去,然后在文章中引用图床返回的图片链接。
这样博客系统只负责写文章、展示文章,图片资源交给图床服务管理。
为什么我需要单独的图床
我的博客主站是用 Halo 搭建的。Halo 本身当然可以上传图片,但我更希望博客主站保持轻量一点,不要把文章系统、插件、主题、图片资源全部混在一起。
拆出图床之后,整个结构会更清晰:
写文章 -> 上传图片到图床 -> 获取图片 URL -> 粘贴到 Halo 文章里
这样做有几个好处。
第一,博客服务器的压力更小。图片、截图、封面图这些静态资源不会全部塞进主站目录。
第二,后续迁移更方便。只要图床域名不变,文章里的图片链接就不会失效。
第三,图片有独立后台管理。上传、删除、目录、标签、公开图库这些操作都可以在图床后台完成,不需要全部依赖博客后台。
第四,图床可以绑定独立域名,比如 imgbed.lovexfan.com。这样图片链接看起来也更统一。技术架构
从源码结构来看,CloudFlare-ImgBed 的整体架构可以分成三层:
前端管理界面
↓
API / Functions 服务层
↓
元数据数据库 + 文件存储后端前端管理界面负责上传、文件列表、系统配置、公开图库等操作。这个仓库里直接包含了构建后的前端产物 frontend-dist。
服务层主要在 functions/ 目录下,采用 Cloudflare Pages Functions 风格组织接口,比如:
/upload 文件上传
/file/xxx 文件访问
/random 随机图片
/dav/ WebDAV
/api/manage/list 文件列表
/api/manage/delete 删除文件
/api/manage/sysConfig 系统配置
/api/manage/apiTokens API Token 管理存储层则分成两部分:一部分是数据库,用来保存文件元数据和系统配置;另一部分是真正的文件存储后端。
这里有一个很重要的设计:文件本体和文件元数据是分开的。
文件本体可以存在 R2、S3、Telegram、Discord、HuggingFace 或 WebDAV 里;而数据库里保存的是文件 ID、文件名、类型、大小、上传时间、目录、标签、存储通道等信息。用户访问图片时,统一访问 /file/xxx,服务再根据元数据判断应该去哪个后端读取文件。
技术栈
这个项目主要使用 JavaScript / Node.js 生态。
从仓库配置可以看到,它使用了:
Node.js 22
Hono
Cloudflare Workers / Pages Functions
Wrangler
Cloudflare D1 / KV
Cloudflare R2
SQLite
AWS SDK S3 Client
WebDAV
Telegram Bot API
Discord API
HuggingFace API
Cloudflare Cache / CDN
如果部署在 Cloudflare 上,它可以使用 Cloudflare 的 D1、KV、R2、Workers Assets 这些能力。
如果使用 Docker 部署,它会通过 Node.js 和 Hono 启动一个本地服务,并模拟 Cloudflare 的运行环境。在 Docker 模式下,它用 SQLite 模拟 D1,用本地文件系统模拟 R2。仓库里的 docker-compose.yml 也很简单:
services:
imgbed:
image: marseventh/cloudflare-imgbed:latest
ports:
- "7658:8080"
volumes:
- ./data:/app/data
restart: unless-stopped这意味着 Docker 部署时,data 目录非常重要。里面会保存本地数据库和本地文件数据,迁移或备份时一定要关注这个目录。
上传流程
当我在后台上传一张图片时,大致流程是这样的:
浏览器选择图片
↓
POST /upload
↓
检查登录状态或 API Token 权限
↓
检查上传 IP、上传目录、文件类型等配置
↓
生成文件 ID 和 metadata
↓
根据配置选择上传通道
↓
上传到 R2 / S3 / Telegram / WebDAV 等后端
↓
把文件记录写入数据库
↓
返回 /file/xxx 访问链接这套逻辑的好处是,前端和博客文章都不需要关心底层存储细节。无论图片实际存在哪里,最终给文章用的都是统一格式的访问链接。
比如我在文章里引用的可能只是:
https://imgbed.example.com/file/xxx.png至于这个 xxx.png 实际在 R2 里,还是在 S3 里,或者在 WebDAV 后端里,对博客来说并不重要。
图片访问流程
图片访问流程也很清晰。
当浏览器访问 /file/xxx 时,服务会先解析文件 ID,然后从数据库读取对应 metadata。接着判断这个文件属于哪个存储通道,再去对应后端读取文件内容。
如果文件在 Cloudflare R2,就从 R2 读取;如果文件在 S3,就通过 S3 API 或配置好的 CDN 地址读取;如果文件在 Telegram,就通过 Telegram Bot API 获取真实文件路径;如果文件在 WebDAV,就通过 WebDAV 地址读取。
最后,服务会把文件内容返回给浏览器,并设置合适的响应头,比如 Content-Type、Cache-Control、ETag、Range 等。
这对图片和视频资源都很重要。比如视频或大文件如果支持 Range 请求,浏览器就可以分段加载,而不是一次性下载完整文件。
管理后台能力
CloudFlare-ImgBed 的后台不只是上传页面,它已经提供了一套比较完整的文件管理能力。
它支持:
文件上传
文件列表
文件删除
文件重命名
目录管理
标签管理
公开图库
随机图片
系统配置
API Token
WebDAV
其中 API Token 对我来说比较实用。后续如果我要接入其他上传工具,不一定要暴露管理员账号,可以单独创建一个只允许上传的 Token。这样安全性会更好。
WebDAV 和随机图片
项目还提供了 /dav/ 路径来支持 WebDAV。WebDAV 可以理解成一种通用文件管理协议,一些客户端可以通过它像操作网盘一样操作文件。
另外还有 /random 随机图片接口。这个功能可以用来做随机背景图、随机头像、随机封面之类的效果。它支持指定目录,也支持按图片方向筛选,比如横图、竖图、方图。
这些功能对普通图床来说不一定是刚需,但说明这个项目已经不只是“上传图片然后返回链接”这么简单,而是往轻量文件管理系统方向扩展了。
使用时需要注意什么
图床很方便,但它本质上也是个人网站的基础设施,所以不能只管上传,不管维护。
首先,重要图片一定要考虑备份。不要把任何图床都理解成“免费无限永久存储”。尤其是接入第三方渠道时,要注意平台规则、容量限制和访问稳定性。
其次,不建议上传特别隐私的内容。即使后台有鉴权,文章里引用的图片链接一旦公开,就要默认它可能被别人访问到。
另外,升级前一定要看项目公告。这个项目在 v2.0、v2.6.2、v2.7.1 都有过比较重要的变化,比如 Docker 镜像结构、数据备份、Cloudflare Pages 构建输出目录等。直接无脑升级有可能踩坑。
还有一点,我本地拉仓库的时候发现它体积比较大,主要是 .git 历史和 README 素材占了不少空间。如果只是部署使用,其实没必要完整 clone 仓库,直接看官方文档、使用 Docker 镜像或者按 Cloudflare 部署说明来就够了。
总结
CloudFlare-ImgBed 对我的博客来说,解决的是图片资源独立管理的问题。
它让我的博客架构变成:
Halo 负责文章内容
图床负责图片管理
存储后端负责文件本体
CDN 负责访问加速这样主站不会因为图片越来越多而变得臃肿,后续迁移博客时也不用担心文章里的图片全部失效。
它不是一个“免费无限存储神器”,而是一个比较完整、可控、适合个人站长使用的媒体资源管理入口。对于经常写博客、插入截图、封面图、教程配图的人来说,把图床独立出来还是很值得的。