Supabase Storage 深度解析:S3 兼容对象存储、图片变换与 RLS 权限体系的实现与自托管实战
Supabase Storage 是一个与 Supabase Auth、Postgres 深度集成的开源对象存储服务,提供 S3 兼容 API、URL 参数式图片变换和基于 Postgres 行级安全策略(RLS)的细粒度访问控制。本文以 Supabase 主仓库(supa/supabase)中的产品文档为骨架,结合 docker/ 目录下的 Compose 编排、配置参考与端到端测试脚本,讲清 Storage 的五项核心能力、三种桶类型,以及在自托管环境中如何配置 file / S3 两种存储后端、接入 imgproxy 图片变换,并用 AWS CLI 完成 S3 协议验证。
一、Supabase Storage 是什么
官方文档(apps/www/content/md/storage.md)对 Supabase Storage 的定位是:一个开源对象存储,原生集成 Supabase Auth 与 Postgres,支持三种面向不同工作负载的桶类型——files(通用资产)、analytics(Apache Iceberg 表格式)、vector(向量嵌入)。
与其他“数据库自带文件字段”的方案不同,Supabase Storage 的权限体系直接复用 Postgres:每个对象都会落入 storage schema 的元数据表中,访问控制通过写在 storage.objects 表上的 RLS 策略实现。这意味着授权逻辑可以用 SQL 表达,并且与 Auth 签发的 JWT(anon / authenticated / service_role 等角色)天然打通。
二、五大核心能力与仓库证据
文档列出了五项 Key Features。下面逐条展开,并给出当前仓库中可以查证的实现依据。
1. S3 兼容:标准 S3 API
Storage 暴露了一个 S3 协议端点,自托管场景下挂载在 /storage/v1/s3 路径。docker/tests/test-s3.sh 中可以看到端点的确切拼法:
S3_ENDPOINT="$BASE_URL/storage/v1/s3"
该测试脚本用 aws cli v2 完整走了一遍 S3 协议栈,验证了 13 个环节:
- ListBuckets —— 列出所有桶;
- CreateBucket —— 建桶后回查 ListBuckets 确认存在;
- PutObject —— 上传文件并校验
upload:输出; - ListObjectsV2 —— 确认对象出现在对象列表中;
- HeadObject —— 用
ContentLength精确比对上传字节数; - GetObject —— 下载后逐字节校验内容一致;
- CopyObject —— 服务端复制,再下载副本验证内容;
- DeleteObject —— 删除后确认对象不再出现;
- 分片上传 —— 构造 7 MB 随机文件触发 multipart 路径(>5 MB 即分片),并双向校验远端与下载尺寸;
- Range 请求 ——
--range "bytes=0-4"只取前 5 字节,验证部分 GET; - 预签名 URL ——
aws s3 presign生成临时 URL 并用curl拉取内容比对; - 鉴权失败路径 —— 故意使用无效凭证,确认返回 403/401 类错误;
- 清理 —— 递归删对象、删桶,并确认桶已消失。
运行方式(脚本头部注释即为准):
# 前置:已按 S3 配置启动自托管实例
docker compose -f docker/docker-compose.yml -f docker/docker-compose.s3.yml up -d
# .env 中需配置 S3_PROTOCOL_ACCESS_KEY_ID / S3_PROTOCOL_ACCESS_KEY_SECRET / REGION
sh docker/tests/test-s3.sh # 默认 http://localhost:8000
sh docker/tests/test-s3.sh <base_url> # 自定义地址
前置依赖是 aws CLI v2 和 jq,脚本会自动检查。凭证从 .env 读取,其中 S3_PROTOCOL_ACCESS_KEY_ID 与 S3_PROTOCOL_ACCESS_KEY_SECRET 是静态 SigV4 密钥,供外部 S3 客户端(aws cli、rclone 等)签名请求使用。
配置参考(docker/CONFIG.md “Storage / S3 backend” 小节)中的关键变量:
| 变量 | 类型 | 说明 |
|---|---|---|
S3_PROTOCOL_ENABLED |
boolean | 启用 S3 兼容 API,默认 true |
S3_PROTOCOL_ACCESS_KEY_ID |
string | 静态 SigV4 Access Key(单租户) |
S3_PROTOCOL_ACCESS_KEY_SECRET |
string | 静态 SigV4 Secret |
S3_PROTOCOL_PREFIX |
string | S3 路由的 URL 前缀,默认空 |
S3_PROTOCOL_ENFORCE_REGION |
boolean | 是否拒绝 region 与 STORAGE_S3_REGION 不符的 SigV4 请求,默认 false |
S3_ALLOW_FORWARDED_HEADER |
boolean | 重建 SigV4 规范化 URL 时是否信任 Forwarded 头,默认 false |
2. 全球 CDN:托管平台的分发能力
文档称托管版资产“从全球 285+ 城市低延迟分发”。需要说明边界:CDN 属于托管平台能力,自托管 Compose 编排中并未包含 CDN 层;自托管下对象由本地 file 后端或外接 S3 后端直出,可通过 RESPONSE_S_MAXAGE(默认 0 秒)为公开响应追加 CDN s-maxage 缓存寿命,为前置反向代理/CDN 预留缓存策略。
3. 图片变换:imgproxy 实时处理
Storage 的图片变换由独立的 imgproxy 服务承担。docker/docker-compose.yml 中可以看到完整的服务定义:
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.30.1
volumes:
- ./volumes/storage:/var/lib/storage:z
environment:
IMGPROXY_BIND: ":5001"
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
IMGPROXY_USE_ETAG: "true"
IMGPROXY_AUTO_WEBP: ${IMGPROXY_AUTO_WEBP}
IMGPROXY_MAX_SRC_RESOLUTION: 16.8
三个关键设计点:
- imgproxy 与 storage 共享同一个数据卷(
./volumes/storage:/var/lib/storage),因此可以直接按本地路径读取源图; IMGPROXY_LOCAL_FILESYSTEM_ROOT: /允许其按绝对路径寻址卷内文件;storage服务声明了对imgproxy的depends_on(docker-compose.yml),并启用变换:ENABLE_IMAGE_TRANSFORMATION: "true"与IMGPROXY_URL: http://imgproxy:5001。
相关配置变量(来自 docker/CONFIG.md “Image transformation” 小节):
| 变量 | 默认值 | 说明 |
|---|---|---|
IMAGE_TRANSFORMATION_ENABLED |
false |
总开关(旧名 ENABLE_IMAGE_TRANSFORMATION) |
IMGPROXY_URL |
无 | imgproxy 基址,启用变换时必填 |
IMAGE_TRANSFORMATION_LIMIT_MAX_SIZE |
2000 |
允许请求的最大变换尺寸(px) |
IMAGE_TRANSFORMATION_LIMIT_MIN_SIZE |
1 |
允许请求的最小变换尺寸(px) |
IMGPROXY_REQUEST_TIMEOUT |
15 |
imgproxy 调用超时(秒) |
IMGPROXY_HTTP_MAX_SOCKETS |
5000 |
imgproxy HTTP agent 最大并发连接 |
对应文档中 “resize, crop, format conversion (WebP, AVIF) via URL parameters” 的能力:变换通过对象 URL 上的参数触发,IMGPROXY_AUTO_WEBP 控制是否自动转 WebP;变换路径还有配套的限流变量 RATE_LIMITER_*(memory / redis 两种驱动,渲染路径默认上限 5 req/s),防止变换端点被滥用。
4. 行级安全:Postgres RLS 即访问策略
文档明确:授权机制是写在 storage.objects 表上的 Postgres RLS 策略,并与 Supabase Auth 集成。这一点在自托管编排里有直接对应:
- storage 服务以专用角色
supabase_storage_admin连接数据库:DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@...(docker-compose.yml),该角色在 docker/volumes/db/roles.sql 中被创建并赋密; - 配置参考中定义了 Storage 识别的三个 Postgres 角色:
DB_ANON_ROLE(默认anon)、DB_AUTHENTICATED_ROLE(默认authenticated)、DB_SERVICE_ROLE(默认service_role)。请求携带的 JWT 决定 Storage 以哪个角色执行 SQL,RLS 策略据此放行或拒绝——这就是“Access policies written in SQL”的落地链路; SERVICE_KEY(service_role JWT)可以绕过 RLS,因此必须按机密管理。
5. Dashboard:可视化文件管理
文档提到控制台支持拖拽上传、文件浏览、多选操作与文件夹管理。自托管下该能力由 Studio 提供:docker/docker-compose.yml 中 studio 服务注入 SUPABASE_ANON_KEY / SUPABASE_SERVICE_KEY 后,即可通过网关(http://localhost:8000)以标准 Supabase 客户端方式操作存储对象,权限模型与托管版一致(同样受 RLS 约束)。
三、三种桶类型(Bucket Types)
文档将桶划分为三类,对应不同工作负载:
Files 桶:日常资产与用户内容
图片、视频、文档、PDF、归档包等通用资产,走全球 CDN(托管)或本地后端(自托管)分发,访问控制细化到 RLS 策略级别。这是绝大多数场景使用的默认桶类型。
Analytics 桶:开放表格式上的分析负载
面向 Apache Iceberg 等开放表格式的大规模分析负载,适合历史数据、时间序列、日志与 ETL 产出物,可选地经 Postgres 查询。
Vector 桶:AI/ML 工作负载
为 AI/ML 场景存储并索引向量嵌入,支持多种距离度量、元数据过滤与快速相似性查询,服务于 RAG 系统与 AI 搜索。
仓库中可以看到 vector 桶在 Storage 服务侧的配置面(docker/CONFIG.md “Tenant features (Vector)” 小节):
| 变量 | 默认值 | 说明 |
|---|---|---|
VECTOR_ENABLED |
false |
是否启用 vector 桶支持 |
VECTOR_MAX_BUCKETS |
10 |
每租户最多 vector 桶数 |
VECTOR_MAX_INDEXES |
20 |
每个 vector 桶最多索引数 |
VECTOR_S3_BUCKETS |
无 | 承载向量索引的 S3 桶列表(逗号分隔) |
VECTOR_BUCKET_REGION |
无 | vector 桶所在 AWS 区域 |
从这些变量可以推断:vector 索引的物理数据落在独立的 S3 桶上(VECTOR_S3_BUCKETS),与 files 桶的数据面解耦。
四、技术细节:协议、限制与上限汇总
综合文档 “Technical Details” 一节与仓库配置,关键参数如下:
| 维度 | 值 | 仓库证据 |
|---|---|---|
| 协议 | S3 兼容 API | test-s3.sh、S3_PROTOCOL_* 变量 |
| CDN | 托管版 285+ 边缘节点 | 文档声明;自托管由反向代理/CDN 自理 |
| 单文件上限(默认自托管) | 50 MB(FILE_SIZE_LIMIT: 52428800) |
docker-compose.yml |
| 单文件上限(托管付费计划) | 500 GB,经 multipart 上传 | 文档声明 |
| S3 分片大小 | 默认 16 MiB,低于 5 MiB 时自动向上钳制 | STORAGE_S3_UPLOAD_PART_SIZE |
| TUS 断点续传 | 分片默认 50 MB,URL 有效期 1 h,路径 /upload/resumable |
TUS_PART_SIZE / TUS_URL_EXPIRY_MS / TUS_URL_PATH |
| 图片变换 | resize / crop / WebP / AVIF 格式转换 | imgproxy 服务 + IMAGE_TRANSFORMATION_* |
| 授权 | storage.objects 表上的 Postgres RLS |
DB_*_ROLE 变量、roles.sql |
上传限额相关的完整变量组(docker/CONFIG.md “Upload limits” 小节):
| 变量 | 默认值 | 说明 |
|---|---|---|
FILE_SIZE_LIMIT / UPLOAD_FILE_SIZE_LIMIT |
必填 | 上传大小上限(字节);Compose 中设为 52428800(50 MB) |
UPLOAD_FILE_SIZE_LIMIT_STANDARD |
0(不限制) |
非断点续传上传的单独上限 |
TUS_PART_SIZE |
50(MB) |
TUS 分片大小 |
TUS_MAX_CONCURRENT_UPLOADS |
500 |
最大并发 TUS 会话数 |
TUS_URL_EXPIRY_MS |
3600000(1 小时) |
TUS 上传 URL 有效期 |
UPLOAD_SIGNED_URL_EXPIRATION_TIME |
60(秒) |
签名上传 URL 默认有效期 |
TUS_LOCK_TYPE |
postgres |
TUS 上传锁的存储后端(postgres / s3) |
另外,STORAGE_EMPTY_BUCKET_MAX(默认 200000)限制单次“清空桶”调用可删除的对象数;REQUEST_URL_LENGTH_LIMIT(默认 7500)约束对象 Key 的 URL 长度——两者都是大规模对象场景下容易踩到的隐性上限。
五、自托管实战:两种存储后端的完整配置
场景 A:默认 file 后端(本地文件系统)
docker/docker-compose.yml 中 storage 服务(镜像 supabase/storage-api:v1.60.4)的默认编排即 file 后端:
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.60.4
depends_on:
db: { condition: service_healthy }
rest: { condition: service_started }
imgproxy: { condition: service_started }
environment:
ANON_KEY: ${ANON_KEY}
SERVICE_KEY: ${SERVICE_ROLE_KEY}
POSTGREST_URL: http://rest:3000
AUTH_JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
STORAGE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
REQUEST_ALLOW_X_FORWARDED_PATH: "true"
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
GLOBAL_S3_BUCKET: ${GLOBAL_S3_BUCKET} # file 后端下为目录名
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
TENANT_ID: ${STORAGE_TENANT_ID}
REGION: ${REGION}
ENABLE_IMAGE_TRANSFORMATION: "true"
IMGPROXY_URL: http://imgproxy:5001
S3_PROTOCOL_ACCESS_KEY_ID: ${S3_PROTOCOL_ACCESS_KEY_ID}
S3_PROTOCOL_ACCESS_KEY_SECRET: ${S3_PROTOCOL_ACCESS_KEY_SECRET}
volumes:
- ./volumes/storage:/var/lib/storage:z
要点:
STORAGE_BACKEND: file时,GLOBAL_S3_BUCKET的语义变为目录名(注释原话:“S3 bucket when using S3 backend, directory name when using 'file'”),docker/.env.example 中示例值为stub;- 数据落在宿主机
docker/volumes/storage下,随卷持久化;file后端的 ETag 算法可用STORAGE_FILE_ETAG_ALGORITHM在md5(默认)与mtime间切换; REQUEST_ALLOW_X_FORWARDED_PATH: "true"让 Storage 在计算对外公开 URL 时信任X-Forwarded-Path,适配网关重写路径的部署;- 健康检查探测
http://storage:5000/status,服务默认端口5000(admin 端口5001)。
数据库侧配置(docker/CONFIG.md “Database” 小节)值得注意的默认值:DATABASE_MAX_CONNECTIONS 每租户连接池 20、DATABASE_STATEMENT_TIMEOUT 30000 ms、DATABASE_CONNECTION_TIMEOUT 3000 ms;若前置 Supavisor/PgBouncer,可用 DATABASE_POOL_URL 接管(此时 DATABASE_MAX_CONNECTIONS 被忽略)。
场景 B:S3 后端(MinIO / RustFS)
官方提供了两份可叠加的 Compose 覆盖文件,启动命令形如:
docker compose -f docker/docker-compose.yml -f docker/docker-compose.s3.yml up -d
docker/docker-compose.s3.yml 引入 MinIO 作为对象后端,编排链为:minio(健康检查 mc ready local)→ minio-createbucket(一次性容器:mc alias set supa-minio http://minio:9000 ... 后执行 mc mb --ignore-existing supa-minio/${GLOBAL_S3_BUCKET})→ storage(依赖建桶容器成功退出)。storage 服务的叠加环境变量:
STORAGE_BACKEND: s3
GLOBAL_S3_ENDPOINT: http://minio:9000
GLOBAL_S3_PROTOCOL: http
GLOBAL_S3_FORCE_PATH_STYLE: true
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
几个必须理解的细节:
GLOBAL_S3_*系列是STORAGE_S3_*的遗留别名,两者等价(见 docker/CONFIG.md);新写配置建议用STORAGE_S3_*;- MinIO 内部地址用路径式寻址(
FORCE_PATH_STYLE: true),这也是自托管 S3 兼容服务的常见要求; - 桶名由
GLOBAL_S3_BUCKET统一,创建容器用--ignore-existing保证幂等,可重复docker compose up; - docker/docker-compose.rustfs.yml 是同一套模式的 RustFS(Rust 实现的 S3 兼容服务)变体,结构与 MinIO 版完全对称(
rustfs→rustfs-createbucket→storage),可视为 MinIO 的替换件,二者择一。
验证与回归
后端切换完成后,用 docker/tests/test-s3.sh 跑一遍 S3 协议冒烟测试是最直接的验收手段——它覆盖建桶、上传/下载/复制/删除、7 MB 分片上传、Range 请求、预签名 URL 与错误凭证拒绝共 13 项检查,全部通过才退出码 0。脚本末尾输出 === Results: N passed, 0 failed === 即为通过标准。
六、小结
- Supabase Storage 的本质是**“Postgres 元数据 + RLS 权限 + 可插拔对象后端(file / S3)+ imgproxy 变换”** 的组合:权限面完全复用 Auth/Postgres,数据面可落在本地文件系统或任意 S3 兼容服务上;
- 三种桶类型(files / analytics / vector)分别对应通用资产、Iceberg 分析表与向量嵌入负载,vector 能力由
VECTOR_*变量族控制,默认关闭; - 自托管落地时,
docker/docker-compose.yml提供 file 后端开箱配置(50 MB 上限、imgproxy 变换、S3 协议端点密钥),docker/docker-compose.s3.yml/docker/docker-compose.rustfs.yml提供 MinIO/RustFS 后端的叠加编排,docker/CONFIG.md 是逐变量的权威参考,docker/tests/test-s3.sh 则给出可复制的验收脚本; - 托管与自托管的能力边界要分清:全球 CDN、500 GB 分片上限等属于托管平台规格;自托管实例的实际上限由
FILE_SIZE_LIMIT、STORAGE_S3_UPLOAD_PART_SIZE等环境变量自行决定。
完整变量清单(Server / Database / JWT / S3 backend / File backend / Image transformation / Upload limits / Rate limiting / Webhook / Vector 各小节)请查阅 docker/CONFIG.md 的 “Storage” 章节;组件清单与生产安全注意事项(默认配置不可直接用于生产、需更换全部默认密钥)见 docker/README.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00