2754 字
14 分钟
Garage + Cloudreve 自建对象存储踩坑实录:从部署到生产
2026-02-10
无标签
TIP

写在前面:主要记录下Garage(对象存储)+ Cloudreve(网盘)部署过程,包含 WebUI 选型误区、CORS 跨域深坑、以及对象存储与网盘系统踩坑过程。适合想自建存储可以参考下。本文基于真实部署环境,重点剖析 Garage 作为轻量级 S3 兼容存储与 Cloudreve 对接时的典型问题,尤其针对 CORS 配置、单节点集群初始化、WebUI 功能局限等易踩坑点提供实操解决方案。

一、需求与选型#

硬件环境:J4125 / 16G 内存 / 多盘位 NAS
核心需求

  • 自建 S3 兼容的对象存储(刚用图床想了解下,且云存储技术也比较火,就想自己部署下)
  • 通过 Web 界面管理文件(类似百度网盘)
  • 支持web登录、分享、在线预览

选型对比

方案优势劣势选择理由
MinIO功能完整、生态成熟已放弃 AGPLv3,转向商业许可开源协议风险,长期维护不确定性高
Ceph企业级、功能强大部署复杂、资源消耗大家用 NAS 环境过于重型
Garage轻量(<50MB 内存)、Rust 编写、S3 兼容单节点需手动初始化、生态工具少资源友好、协议干净、适合家庭场景
Cloudreve国产、中文文档完善、支持多存储后端社区活跃度一般对接 S3 简单、符合国人使用习惯
AList轻量、支持多网盘聚合无用户体系、无分享功能不满足多用户协作需求

最终架构

  • 存储层:Garage(轻量级对象存储,Rust 编写,支持 S3 API)
  • 应用层:Cloudreve(网盘系统,对接 Garage S3 API)
  • 数据流:用户 → Cloudreve(Web)→ Garage(S3 API)→ 本地磁盘

二、Garage 部署实战#

Garage 是一个轻量级的对象存储系统,支持 S3 API,且配置简单。早先博主打算是部署 MinIO,但是目前 MinIO 已经放弃开源版本的维护,而 Garage 则是一个活跃的项目(Apache 2.0 协议),且支持 S3 API。可方便地与 Cloudreve 等网盘系统对接,存储图片、视频等文件。其核心特性包括:

  • 去中心化设计:天然支持多节点集群,数据自动分片分布
  • 资源占用极低:单节点常驻内存 <50MB,适合低功耗 NAS
  • 强一致性:基于 CRDT 实现元数据同步,避免脑裂问题

2.1 基础配置#

Garage 的 docker-compose.yml(包含了一个用来管理 Garage 的 WebUI,其实没多大用,聊胜于无):

services:
garage:
image: dxflrs/garage:v2.0.0
container_name: garage
volumes:
- ./garage.toml:/etc/garage.toml
- ./meta:/var/lib/garage/meta
- ./data:/var/lib/garage/data
restart: unless-stopped
ports:
- 3900:3900 # S3 API
- 3901:3901 # RPC(集群通信)
- 3902:3902 # Metrics(可选)
- 3903:3903 # Admin API
webui:
image: khairul169/garage-webui:latest
container_name: garage-webui
restart: unless-stopped
volumes:
- ./garage.toml:/etc/garage.toml:ro
ports:
- 3909:3909 # 注意有些教程会说webui默认端口是8080,实际不是
environment:
API_BASE_URL: "http://garage:3903"
S3_ENDPOINT_URL: "http://garage:3900"

标准的 Garage 是不带 WebUI 的,要配置一般需要使用 S3 的客户端管理工具,如 AWS CLI。如果不用工具就只能 curl 手动模拟配置了。

garage.toml 关键配置(避坑重点):

metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1 # 单节点必须设为1,否则无法写入;生产环境建议≥2
# RPC 必须绑定 0.0.0.0,否则外部命令连不上
rpc_bind_addr = "0.0.0.0:3901"
rpc_public_addr = "0.0.0.0:3901" # 注意:公网部署需替换为实际IP
rpc_secret = "你的64位十六进制密钥" # 节点间通信密钥,所有节点需一致
[s3_api]
s3_region = "garage" # 必须与Cloudreve配置一致,否则CORS校验失败
api_bind_addr = "[::]:3900" # IPv6兼容绑定,同时监听IPv4
[admin]
api_bind_addr = "[::]:3903"
# 注意:admin_token 必须加引号!
admin_token = "你的随机Token"
# 随机Token可以用 openssl rand -hex 32 生成
# 或者用 openssl rand -base64 32 生成

踩坑 1:RPC 绑定地址
默认配置是 127.0.0.1:3901,这会导致宿主机无法执行 garage 命令管理集群。必须改为 0.0.0.0:3901 并映射端口。

WARNING

切记注意网络配置,确保 webui 可以访问到 garage 的 rpc 端口。Docker Compose 中建议使用自定义网络(networks)避免 DNS 解析问题。

2.2 集群初始化(必须步骤)#

Garage 即使单节点也需要初始化布局,否则无法创建 Bucket。其设计哲学是”所有节点平等”,通过布局(layout)定义数据分布策略:

Terminal window
# 获取节点 ID(64位十六进制字符串)
NODE_ID=$(docker exec garage /garage node id | grep -oP '[a-f0-9]{64}')
# 分配角色(单节点作为存储节点,容量 1TB 示例)
# -z dc1:指定区域(单节点可任意命名)
# -c 1T:承诺容量(影响数据分布权重)
# -t 1T:阈值容量(超过后拒绝写入)
docker exec garage /garage layout assign $NODE_ID -z dc1 -c 1T -t 1T
# 应用配置(必须指定 --version,首次为1)
docker exec garage /garage layout apply --version 1

节点 ID 在 WebUI 中需要用到,也可通过 docker exec garage /garage node list 查看。

NODE_ID


三、WebUI 的深坑:从 Filestash 到 garage-webui#

3.1 Filestash:通用但不适合#

最初尝试用 Filestash 作为 Garage 的 WebUI:

  • 问题:Filestash 是通用 S3 客户端,配置复杂(需手动填写 endpoint/region/key)
  • 致命伤:无法直接管理 Garage 的 Bucket/Access Key,只能浏览已创建的存储
  • 额外成本:每次新增 Bucket 需重新配置连接,不适合多租户场景

3.2 garage-webui:专用但鸡肋(因为不支持配置 CORS)#

转而使用 khairul169/garage-webui(专为 Garage 开发的 WebUI):

踩坑 2:环境变量名混乱
该项目文档缺失,实际需使用:

  • API_BASE_URL(不是 GARAGE_RPC_URL)→ 对应 garage 的 admin 端口(3903)
  • S3_ENDPOINT_URL(不是 GARAGE_S3_URL)→ 对应 garage 的 S3 端口(3900)

踩坑 3:功能残缺
该 WebUI 只能创建 Bucket 和 Access Key,无法上传文件、无法设置 CORS,甚至不支持删除操作。GitHub 上有用户提交 CORS 管理 PR 但作者未合并。项目最后一次更新为 2023 年,维护状态堪忧。

TIP

替代方案建议:生产环境建议直接使用 AWS CLI 或 s3cmd 管理 Garage,WebUI 仅作临时参考。若需图形化管理,可考虑 MinIO Console(但需额外部署 MinIO Gateway 模式,增加复杂度)。


四、Cloudreve 对接与 CORS 地狱#

CAUTION

重要提醒:博主最先尝试用 1panel 部署 Cloudreve,但是发现 1panel 的 Cloudreve 版本部署一直失败,疑似对应版本构建有问题(镜像内缺失 ffmpeg 等依赖),最后采用的是官方最新版本的 Cloudreve Docker 镜像构建。

4.1 基础对接配置#

Cloudreve 存储策略设置:

配置项说明
存储方式S3选择 S3 兼容
Endpointhttp://192.168.31.132:3900Garage S3 端口(必须用宿主机IP,不能用 localhost
Regiongarage与 garage.toml 一致,Cloudreve 默认 us-east-1 会导致签名错误
Access KeyGarage 生成的 Key ID通过 garage key new -c cloudreve 创建专用密钥
Secret KeyGarage 生成的 Secret同上
Bucketcloudreve需提前通过 CLI 创建
使用路径样式✅ 勾选关键!Garage 不支持虚拟主机风格(bucket.endpoint)
上传方式服务端中转或浏览器直传直传需正确配置 CORS,否则失败
WARNING

路径样式说明:勾选后 URL 格式为 http://endpoint/bucket/key;不勾选为 http://bucket.endpoint/key。Garage 仅支持前者,否则 Cloudreve 会返回 403 错误。

4.2 CORS 跨域:最大的坑#

现象:浏览器上传报 CORS Missing Allow OriginNetwork Error,控制台显示 OPTIONS 请求 403。

原理说明
当 Cloudreve 前端(如 https://pan.example.com)尝试直传文件到 Garage(http://192.168.31.132:3900)时,浏览器会先发送 OPTIONS 预检请求。若 Garage 未返回正确的 CORS 头(如 Access-Control-Allow-Origin),浏览器将阻断实际上传请求。

排查过程

  1. Region 不匹配:Cloudreve 默认 us-east-1,必须改为 garage(否则签名计算错误,返回 403)
  2. CORS 未配置:Garage 不支持在配置文件中设置全局 CORS,必须通过 S3 API 为每个 Bucket 单独设置:
Terminal window
# 安装 aws-cli(v2 推荐)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
# 配置凭证(交互式)
aws configure --profile garage
# AWS Access Key ID: [输入 Garage 生成的 Key ID]
# AWS Secret Access Key: [输入 Secret]
# Default region name: garage
# Default output format: json
# 设置 CORS 规则(允许 Cloudreve 域名,生产环境请收紧)
aws s3api put-bucket-cors \
--bucket cloudreve \
--cors-configuration '{
"CORSRules": [{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS"],
"AllowedOrigins": ["https://pan.example.com", "http://localhost:5212"],
"ExposeHeaders": ["ETag", "x-amz-version-id"],
"MaxAgeSeconds": 86400
}]
}' \
--endpoint-url http://192.168.31.132:3900 \
--profile garage
# 验证配置是否生效
aws s3api get-bucket-cors --bucket cloudreve --endpoint-url http://192.168.31.132:3900 --profile garage

直传 vs 中转对比

方式优点缺点适用场景
浏览器直传减轻服务器带宽压力需配置 CORS、暴露 S3 endpoint大文件上传、公网部署
服务端中转无需 CORS、安全性高消耗 Cloudreve 服务器带宽内网环境、小文件为主
TIP

调试技巧:使用浏览器开发者工具 → Network 标签,筛选 OPTIONS 请求,查看响应头是否包含 Access-Control-Allow-*。若返回 403,大概率是签名错误(检查 region/key);若返回 200 但无 CORS 头,则是 Bucket 未配置 CORS。


五、存储去重的思考:对象存储 ≠ 网盘#

原本以为 Garage 会自动去重,但是实际上 Garage 是一个对象存储,不会自动去重。之前用过阿里云和腾讯云的对象存储,以为去重功能是标配,实际这是很大误区。一般的文件去重都是商业公司出于成本和服务性能考虑而实现的。需要将文件切块计算哈希值,然后对比是否有重复块。如果有重复块,就只存储一个块,其他块的引用指向这个块,在多节点集群下会比较复杂,开源存储一般没有。

现象:相同图片上传两次,Garage 中占用两份空间。

本质原因

  • 对象存储层(Garage/MinIO/S3):以 Object Key(路径)为唯一标识,内容哈希仅用于传输校验,不用于存储去重
  • 网盘应用层(Cloudreve):虽有哈希计算,但仅用于秒传检测(前端计算哈希 → 服务端比对 → 若存在则跳过上传),实际存储仍由后端对象存储决定,Cloudreve 本身不实现块级去重

去重方案对比

方案去重级别实现位置代表产品
文件级去重整文件哈希应用层Cloudreve 秒传、Nextcloud
块级去重固定/变长分块存储层Seafile、ZFS dedup
无去重--Garage、MinIO、AWS S3

实际建议

  • 家庭场景重复文件较少,去重收益有限(NAS 空间成本远低于计算开销)
  • 若确需去重,建议:
    1. 使用 Seafile 自建(原生支持块级去重)
    2. 底层文件系统启用 ZFS dedup(内存消耗大,需 5 倍 RAM)

六、安全加固建议(补充)#

  1. 网络隔离:Garage S3 端口(3900)不应直接暴露公网,应通过 Cloudreve 服务端中转或反向代理(Nginx)限制来源 IP
  2. HTTPS 强制:Cloudreve 前端必须启用 HTTPS,否则浏览器会阻止非安全上下文的直传请求
  3. Access Key 权限:为 Cloudreve 创建专用密钥,并通过 garage key allow-bucket 限制仅访问指定 Bucket
  4. 防火墙规则:仅开放 Cloudreve Web 端口(5212),Garage 端口仅允许本机访问

参考链接

Garage + Cloudreve 自建对象存储踩坑实录:从部署到生产
https://blog.wentianlivas.top/posts/garage/
作者
Andy
发布于
2026-02-10
许可协议
CC BY-NC-SA 4.0