[开源] PicHost - 自托管个人轻量图床

3131 字
16 分钟
[开源] PicHost - 自托管个人轻量图床

博客写久了,配图是刚需。市面上图床不少:界面花花绿绿、相册管理、社交分享、付费套餐一应俱全。对我这种只给自己博客用的场景来说,功能越多反而越重——我更在意几件事:数据在自己手里不依赖某家图床的政策和续费自己不删就尽量一直能访问

最初的想法很朴素:蹭 Cloudflare 大善人 的免费额度,对象存储用 R2,自己上传的图片落在自己的桶里。不想维护 VPS,也不想再背一套 PHP + MySQL 图床程序。于是有了 PicHost 的第一版:Nuxt 管理后台 + R2 私有桶 + Worker 出图,管理域和图片域拆开,Referer 白名单挡一挡普通盗链。

跑了一阵子,博客接上了 Twikoo 评论,评论区发图也要走图床。Twikoo 文档里列了不少「官方支持」的图床方案,不少要付费或绑第三方——和我「自建、少依赖、能长期放」的初衷不太合拍。于是给 CF 版补了 EasyImage 2.0 兼容接口和独立上传 Token,正文配图走管理后台,评论配图走脚本 Token,同一套 R2、同一个 img 域出图。

但个人博客图床真正折磨人的,渐渐不是「上传一张图」,而是运维细节:双域配置、Worker 变量每次 deploy 被 Dashboard 覆盖、R2 binding 和环境变量散落各处……每次 GitHub Actions 跑完,博客插图就可能全站 403。修好了,下次 deploy 又翻车。

所以我把它推倒重来了。现在的 PicHost(v1.2.2)是 Docker 自托管:一条命令跑起来,数据全在挂载的 /data 里,SQLite 管用户和设置,图片统一落在 images/。部署细节、API、双域名与迁移说明已迁到 用户文档(VitePress,GitHub Pages 托管);这篇只记我为什么改、改成了什么样、自己怎么用的

O96u
/
PicHost
Lightweight self-hosted image hosting — Docker, multi-user, gallery & API, local disk or S3-compatible storage.
GPL-3.0
TypeScript

项目是怎么长出来的#

阶段我当时的诉求PicHost 怎么回应
v0(CF 版,已归档)博客配图,CF + R2,自持、长期保存Pages 上传 + R2 私有 + Worker CDN
接上 Twikoo 后评论发图,不想用收费官方图床EasyImage 2.0 兼容 + 上传 Token
v1.0少运维、数据能整包带走、偶尔给别人开账号Docker 零配置引导、本地盘 + SQLite、多用户
v1.1想接回 R2 / 对象存储,管理域与出图域再拆开S3 多后端、/storage 管理页、双域名隔离
v1.2(当前)目录别越分越乱,升级别丢图;文档要独立站统一 images/、CLI 迁移、VitePress 文档站

市面上那些「花花绿绿」的图床依然很好,只是诉求不同:我要的是轻量、和 Firefly + Twikoo 咬得上,而不是天天跟 Cloudflare Dashboard 较劲。


为什么从 Cloudflare 版推倒重来#

CF 版并非不能用,对个人博客量级也够用。让我下决心重写的是这几件事叠在一起:

双域拆分维护成本高。 管理后台在 pic 域,出图在 img 域,环境变量、IMAGE_BASE_URL、Worker 路由、R2 binding 要同时对齐。改一处漏一处,返回的 Markdown 链接就和防盗链规则对不上。

deploy 会「悄悄改配置」。 最大的坑是 ALLOWED_REFERER_HOSTS 写进 wrangler.jsoncvars——每次 CI deploy 都会用仓库里的值覆盖 Dashboard 手工加的博客域名,全站插图 403。根治办法是从 wrangler 删掉 vars,但这类细节要踩过才知道。

数据不在一个「我能直接摸到」的地方。 R2 桶里的对象好备份,但用户、设置、会话散落在 CF 各处的配置里;想迁到 NAS 或家里小主机,得重新拼一套。

v1.0 的选择很直接:单进程、单域、单数据目录。Nuxt 4 管界面,Nitro 管 API 和出图,Referer 防盗链还在,Twikoo 兼容接口还在,架构变成「能 docker run 就跑、能 tar /data 就迁」。后来的 v1.1 又把对象存储和双域名接回来了,但都在同一个 Docker 实例里完成,不再绑死在 CF Dashboard。

浏览器 / Twikoo

https://pic.example.com
反代 → :6892

登录 / 上传 / 图库 / 统计 / 存储 / 设置

/api/images/*、/api/index.php

GET /images/... 或短链
Referer 白名单 + 本地/对象存储

/data

pichost.db

images/
全部图片 + blog/、twikoo/ …

浏览器 / Twikoo

https://pic.example.com
反代 → :6892

登录 / 上传 / 图库 / 统计 / 存储 / 设置

/api/images/*、/api/index.php

GET /images/... 或短链
Referer 白名单 + 本地/对象存储

/data

pichost.db

images/
全部图片 + blog/、twikoo/ …


界面长什么样#

v1.2 后台基于 Nuxt UI,默认暗黑主题,中英文可切换。登录页加了滑动拼图验证;上传偏好藏在卡片背面,翻转可调客户端预压缩、自动复制链接、按用户自动删除。下面按我用到的页面过一遍——截图来自 仓库,与当前 v1.2.2 一致。

上传#

主界面就是上传区:拖拽、点击、Ctrl+V 粘贴都行。上传完一键复制直链 / Markdown / HTML,写博客时基本不用切窗口。

PicHost 上传页
PicHost 上传页

存储管理#

可以挂多个 S3 兼容后端(R2、COS、OSS、AWS S3),在 存储 页添加实例、设默认写入后端,直链可选 PicHost 代理或 302 到 CDN。

PicHost 存储管理
PicHost 存储管理

图库与统计#

图库分页浏览、按文件名搜索、批量删除;管理员能看到每张图的上传者标签,还能按存储后端筛选。统计页看上传/删除趋势和目录分布,偶尔清图库时有个数。

PicHost 统计与图库
PicHost 统计与图库

设置#

Referer 白名单、公网域名、全局上传 Token 都在这里配;支持单域或 双域名分离SITE_BASE_URL + IMAGE_BASE_URL),还能切换按年/月分组或扁平保存路径。v1.2.2 修了双域名保存时误清空管理域、未知 Host 绕过隔离等问题。

PicHost 系统设置
PicHost 系统设置

相比 CF 版,Docker 版多出来的、我自己用得上的几块:

  • 多用户:账号密码登录(滑动验证),管理员可开放注册;普通用户只看到自己的图。以前 CF 版是单人密钥,没法给朋友开个上传账号。
  • 多后端存储:本地盘和云桶可以混用,新上传走默认后端,老图按索引路由,不必为了 R2 再维护一套 CF Worker。
  • 双域名分离:管理域和图片域拆开,中间件自动隔离——网站域访问不了后台,图片域访问不了非图片路径。
  • 操作日志:上传/删除有记录,管理员可按用户筛选,偶尔查「谁删了这张图」有用。
  • 上传偏好:卡片翻转设置里可调客户端预压缩、上传后自动复制链接、按用户自动删除——写博客时少点几下。
  • 统计页:上传/删除趋势、目录分布,管理员还能看到注册用户数。个人图床不至于天天看,但偶尔清图库有个数。
  • 服务端 WebP:sharp 在入库前转码,比纯浏览器端压缩稳,大图体积也能压下来。
  • 文档站o96u.github.io/PicHost 中英文指南,环境变量、API、双域名、Twikoo、迁移与更新日志都集中在这里,README 只留产品宣传。

复制直链 / Markdown / HTML、拖拽粘贴上传、图库搜索批量删——这些基础能力保留着,没为了「重写」而砍掉。


我怎么部署的#

我现在用 Docker,核心就这一条:

Terminal window
docker run -d \
--name pichost \
-p 6892:6892 \
-v ./data:/data \
--restart unless-stopped \
muxui/pichost:latest

浏览器打开 http://<主机IP>:6892,首次访问走 Web 引导创建管理员,不用预先写 ADMIN_SECRET——这是和 CF 版体验差别最大的一点。以前要在 Dashboard 配一串 Secret,配错就登录不了;现在容器起来就能用。

前面挂了 Nginx 反代,HTTPS 终止在反代层,client_max_body_size 我设了 12m。公网访问记得配 IMAGE_BASE_URL(单域)或 SITE_BASE_URL + IMAGE_BASE_URL(双域分离),否则 API 返回的 Markdown 链接会带内网地址,粘贴到文章里读者打不开。双域 Nginx 示例见文档站 双域名分离

从 v1.1 升到 v1.2#

v1.2.0 起所有本地图片统一进 data/images/,旧版 data/blog/data/twikoo/ 等与 images 并列的顶层目录要先迁移:

Terminal window
# 预览,生成 data/mapping.json(旧 key → 新 key)
docker exec pichost migrate
# 确认后搬文件
docker exec pichost migrate --apply
# 拉新镜像并重启;启动日志会打印索引同步

升级前记得整包备份 data/。若图片早已全在 images/ 下、或只用对象存储,可跳过 CLI,重启后启动同步会自动补索引。详细说明见文档站 v1.2 迁移

数据目录#

/data
├── pichost.db # 用户、会话、设置、storage_backends、images 索引
└── images/ # 全部图片(本地后端时)
├── blog/ # 我自定义的博文目录(迁移后)
├── twikoo/ # 评论配图(迁移后)
└── 2026/08/… # 或扁平保存:images/随机ID.webp

新上传路径为 images/随机ID.webpimages/年/月/随机ID.webp(可在设置里切换扁平/分组);元数据在 SQLite images 表。备份就整包拷 /data(含云桶数据若用了对象存储),迁机器也是挂同一个卷。

忘记密码用 docker exec pichost reset-password [用户名],终端吐随机新密码。具体环境变量、API 字段、开发命令——以 用户文档 为准,这里不重复抄说明书。


Twikoo 评论配图怎么接#

这条链路从 CF 版沿用下来,配置没变多少:

Twikoo 配置项填什么
IMAGE_CDNeasyimage
IMAGE_CDN_URLhttps://你的域名/api/index.php
IMAGE_CDN_TOKEN与图床 API_UPLOAD_TOKEN 相同

评论发图走 POST /api/index.php,表单里 token + image;展示时图片 URL 是 /images/... 直链(开启隐藏前缀时也可能是 /xxx.webp 短链)。

Referer 白名单仍然要配。 和 CF 版一样,把博客域名(muxui.comwww.muxui.com分别加)写进 ALLOWED_REFERER_HOSTS,否则读者在评论区看到的图会 403,自己后台预览却正常——这种「只有我电脑能看」的错觉,我踩过不止一次。双域名模式下,网站域与图片域主机名会自动放行。

正文配图我习惯在后台上传到 blog/ 目录,评论图自动进 twikoo/;v1.2 起它们都在 images/blog/images/twikoo/ 下,删图库时好区分来源。若从旧版升级,记得跑一遍 migrate 并对照 mapping.json 替换文章里的旧外链。


版本更新#

  1. 2026-08-30
    v1.2.2

    双域名:修复设置页保存误清空管理域、配置值与 request origin 混淆、未知 Host 绕过隔离;设置 API 拆分 siteBaseUrl / imageBaseUrleffective*;文档站新增 更新日志

  2. 2026-08-29
    v1.2.1

    登录滑动拼图验证;VitePress 文档站(中英文)发布至 GitHub Pages o96u.github.io/PicHost;README 精简为产品宣传页;修复统计页删除后图库不刷新等问题。

  3. 2026-08-28
    v1.2.0

    统一本地图片到 data/images/;遗留 blog/twikoo/ 等并列目录通过 docker exec pichost migrate 迁移。启动时自动扫描磁盘补索引、归一化遗留 key、清理孤儿记录。详见 v1.2 迁移

  4. 2026-08-28
    v1.1.5

    修复 API 上传后图库预览偶发需刷新才显示;出图前校验文件存在;404/403 响应禁止 CDN 缓存。

  5. 2026-08-28
    v1.1.4

    上传路径支持按年/月分组或扁平保存;双域名下网站域禁止直链出图;可隐藏外链中的 images/ 前缀;设置页分组重构。

  6. 2026-08-27
    v1.1.3

    单实例双域名分离(SITE_BASE_URL / IMAGE_BASE_URL);设置页检测 GitHub 最新版本;许可证改为 GPL-3.0。

  7. 2026-08-26
    v1.1.2

    操作日志页(/logs);图库按存储后端筛选;上传 IP / API Token 限流(15 分钟窗口)。

  8. 2026-08-26
    v1.1.0

    S3 兼容多后端(R2 / COS / OSS / AWS S3);/storage 存储管理页;混合直链 proxy / publicimages 索引表改查 SQLite。

  9. 2026-08-23
    v1.0

    Docker 自托管首发:本地盘 + SQLite、多用户、Web 引导创建管理员、Twikoo 兼容、服务端 WebP、统计与上传偏好。CF R2 版归档至 cloudflare 分支。

  10. 2025 前后
    v0(CF 版,已归档)

    Nuxt + R2 + Worker 双域出图;为 Twikoo 补 EasyImage 2.0 接口。运维成本高,后推倒为 Docker 版。


写在最后#

回头看,PicHost 的演进路径很清晰:先为博客配图(自持、尽量不删即长期在),再为 Twikoo 评论配图(自己适配 EasyImage 接口),最后把运维成本打下来(Docker、本地盘、单域),v1.1 接回对象存储与双域v1.2 把目录模型收拢到 images/ 并补上独立文档站

CF 版教会我 Referer 防盗链和双 Token 分离;Docker 版教会我图床不必绑在某家云上——NAS、VPS、家里小主机,能跑 Docker、能挂卷,就能把图床和数据一起带走。

仓库:github.com/O96u/PicHost(GPL-3.0)· 文档:o96u.github.io/PicHost。部署细节、API、cURL 示例以文档站为准;这篇只是我自己的改版记录。若你也在 Firefly + Twikoo 这条路上,对接有问题欢迎 Issue 或评论区聊。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

[开源] PicHost - 自托管个人轻量图床
https://muxui.com/posts/pichost-cloudflare-r2-image-host/
作者
Muxui
发布于
2026-08-03
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
[开源自荐] DBX - 20MB 开源数据库客户端
软件推荐开源数据库客户端 DBX:约 20MB、Apache-2.0、一个窗口连 80+ 种库。和 Navicat 比,差在许可、体积、部署和 AI/MCP。日常查改够用,企业建模报表还是 Navicat 更稳。
2
YOLO训练麻将(Mahjong)识别,导出ONNX
原创本文基于 Roboflow Universe 数据集(例如 $1 )与仓库内脚本,在 Windows 上完成训练,并导出 ONNX 供后端推理。下文配图均为本机实测截图,便于对照。快速体验 小程序 1\. 环境要求 \ Windows 10
3
基于 Cloudflare Workers + Telegram Bot + D1 的双向匿名聊天系统完整实现
原创在本篇文章中,我将分享一个基于 Cloudflare Workers + Telegram Bot + D1 数据库 的完全无服务器(Serverless)聊天系统。该机器人支持用户匿名与管理员双向通信,具备首访验证、自动诈骗检测、屏蔽管理
4
从 WordPress 到 Astro迁移实战全记录
原创记录将 Muxui 从 WordPress(B2 Pro)完整迁移到 Astro 静态博客主题 Firefly 的全过程:导出 WXR、HTML 转 Markdown、图床改写、友链与导航定制、Twikoo 评论迁移,以及迁移中踩过的坑。
5
自建 Headscale + DERP 全流程实战记录
原创记录一次完整、可上线、可长期运行的 Headscale + 自建 DERP 搭建过程。 本文不是“能跑就行”的教程,而是 生产可用、已多客户端验证 的配置方案。 简介 Tailscale(Headscale)就是组建一个大的局域网,可以将你
随机文章随机推荐

评论区

Profile Image of the Author
Muxui
写代码、记笔记,把折腾过的东西留给未来的自己。
公告
欢迎来到我的博客!这是一则示例公告。
统计
分类
标签
站点统计
文章
22
分类
3
标签
26
总字数
55,208
运行时长
0
天气预报
定位中...
----
高温 --°C / 低温 --°C
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.6
文章许可
CC BY-NC-SA 4.0