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" # 注意:公网部署需替换为实际IPrpc_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)定义数据分布策略:
# 获取节点 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 查看。

三、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 兼容 |
| Endpoint | http://192.168.31.132:3900 | Garage S3 端口(必须用宿主机IP,不能用 localhost) |
| Region | garage | 与 garage.toml 一致,Cloudreve 默认 us-east-1 会导致签名错误 |
| Access Key | Garage 生成的 Key ID | 通过 garage key new -c cloudreve 创建专用密钥 |
| Secret Key | Garage 生成的 Secret | 同上 |
| Bucket | cloudreve | 需提前通过 CLI 创建 |
| 使用路径样式 | ✅ 勾选 | 关键!Garage 不支持虚拟主机风格(bucket.endpoint) |
| 上传方式 | 服务端中转或浏览器直传 | 直传需正确配置 CORS,否则失败 |
WARNING路径样式说明:勾选后 URL 格式为
http://endpoint/bucket/key;不勾选为http://bucket.endpoint/key。Garage 仅支持前者,否则 Cloudreve 会返回 403 错误。
4.2 CORS 跨域:最大的坑
现象:浏览器上传报 CORS Missing Allow Origin 或 Network Error,控制台显示 OPTIONS 请求 403。
原理说明:
当 Cloudreve 前端(如 https://pan.example.com)尝试直传文件到 Garage(http://192.168.31.132:3900)时,浏览器会先发送 OPTIONS 预检请求。若 Garage 未返回正确的 CORS 头(如 Access-Control-Allow-Origin),浏览器将阻断实际上传请求。
排查过程:
- Region 不匹配:Cloudreve 默认
us-east-1,必须改为garage(否则签名计算错误,返回 403) - CORS 未配置:Garage 不支持在配置文件中设置全局 CORS,必须通过 S3 API 为每个 Bucket 单独设置:
# 安装 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 空间成本远低于计算开销)
- 若确需去重,建议:
- 使用 Seafile 自建(原生支持块级去重)
- 底层文件系统启用 ZFS dedup(内存消耗大,需 5 倍 RAM)
六、安全加固建议(补充)
- 网络隔离:Garage S3 端口(3900)不应直接暴露公网,应通过 Cloudreve 服务端中转或反向代理(Nginx)限制来源 IP
- HTTPS 强制:Cloudreve 前端必须启用 HTTPS,否则浏览器会阻止非安全上下文的直传请求
- Access Key 权限:为 Cloudreve 创建专用密钥,并通过
garage key allow-bucket限制仅访问指定 Bucket - 防火墙规则:仅开放 Cloudreve Web 端口(5212),Garage 端口仅允许本机访问
参考链接:
- Garage 官方文档:https://garagehq.deuxfleurs.fr/
- Cloudreve 对接 S3 文档:https://docs.cloudreve.org/
- AWS CLI 配置 Garage:https://garagehq.deuxfleurs.fr/docs/cookbook/s3-clients/