SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-27

如何在 VPS 上用 Docker 自托管 Supabase

在自有 VPS 上运行官方 Supabase Docker Compose 堆栈,了解必须替换的默认密钥、约 14 个服务的作用、实际内存需求、备份方法,以及安全更新堆栈的步骤。

构建内容

自托管 Supabase 是指在自己的服务器上运行官方 Docker Compose 堆栈,其中包括 Postgres、前置 REST API、身份验证服务、文件存储、实时 WebSocket 和 Studio 控制面板。您只需克隆一个仓库、编辑一个 .env 文件,然后启动约十四个容器。这些容器共同提供一个由您控制的 Supabase 项目。

安装过程很短。真正容易出问题的是 .env 文件。该文件包含仓库中公开发布的演示密钥,使用这些默认值启动的堆栈会对发现它的任何人开放。本指南介绍必须替换的密钥、各服务的用途、堆栈实际需要多少内存,以及如何在不删除数据库的情况下更新堆栈。

如果您不熟悉 Compose,请先阅读 VPS 上的 Docker Compose 基础知识。以下内容假设 docker compose version 已能输出版本信息。

实际包含哪些组件

Supabase 不是单个程序。Compose 文件会在同一网络中启动一组独立服务。了解每个服务的作用后,面对一长串容器名称时,才能进行有效排查。

  • db 是加载了 Supabase 扩展的 PostgreSQL。其他所有服务都会与它通信。如果此容器运行不正常,其他服务也会全部失败。
  • kong 是 API 网关。它监听 8000 端口,并将 /rest/v1//auth/v1//storage/v1/ 路由到正确的后端。只有这个容器应该对外暴露。
  • rest 是 PostgREST。它读取 Postgres 架构并将其作为 REST API 提供,因此新建一个表后,无需编写代码就会生成新的端点。
  • auth 是 GoTrue。它签发用于标识用户的 JSON Web Token(JWT)。
  • storageimgproxy 负责文件上传和图像缩放。
  • realtime 通过 WebSocket 流式传输数据库变更。
  • studiometa 分别是控制面板及其背后的管理 API。
  • analytics(Logflare)和 vector 负责收集日志,supavisor 是 PostgreSQL 连接池。

这就是下面资源数量如此配置的原因。你运行的不只是一个数据库,而是一个数据库加上十多个支持服务。

内存规划:准备 8 GB

截至 July 2026,在全新安装且尚未计入您自己的数据和流量时,这套服务栈空闲时约占用 2.5 到 3 GB 常驻内存。分析服务和 Studio Node.js 进程是占用内存最多的两个独立组件。2 GB 服务器会先启动容器,随后其中一个通常会被内核的 out-of-memory killer 终止,通常是 analyticsdb;其表现是容器不断重启,并返回退出码 137。

对于任何您要依赖的环境,请配置 8 GB 内存和 4 vCPU。如果接受重型查询与 Studio 会话同时运行时速度较慢,4 GB 也可用于个人开发实例。磁盘空间同样重要,因为 Postgres、存储卷和日志数据都位于项目目录下。先从 40 GB 开始,并持续监控使用情况。在选择方案前统计服务数量,是自托管任何服务时都值得保持的习惯,因为 PhotoPrism 和 Immich 的实际最低内存需求远高于其快速入门页面所暗示的水平。

安装:克隆官方仓库

受支持的方式是将主仓库中的 docker 目录复制到您自己的项目目录中。这样分离目录很重要,因为后续执行 git pull 时不会覆盖您的 .env

git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pull

docker compose pull 会下载数 GB 的镜像。命令结束时,每个服务都应标记为 Pulled。此处出现 manifest unknown 错误,表示上游已删除固定的镜像标签。解决方法是重新拉取仓库的较新副本,而不是手动修改标签。

首次启动前必须更改的密钥

请在启动服务栈前完成这些操作,不要等到启动后再做。部分值会在首次启动时写入数据,因此之后再修改就意味着必须重置数据库。

代码仓库提供了一个生成器,可正确生成所有值,包括必须使用新的 JWT 密钥签名的两个 API 密钥。

sh utils/generate-keys.sh --update-env

该脚本会将 JWT_SECRETANON_KEYSERVICE_ROLE_KEYSECRET_KEY_BASEREALTIME_DB_ENC_KEYVAULT_ENC_KEYPG_META_CRYPTO_KEY 以及 Logflare 令牌的新值写入 .env。它需要 openssl;任何标准 Ubuntu 镜像都包含该工具。

有两个值不会由脚本设置,您必须在 .env 中手动编辑:

  • POSTGRES_PASSWORD。只能使用字母和数字。此处的标点符号会破坏多个服务通过拼接字符串生成的连接字符串。故障表现为身份验证错误,而不是解析错误,容易导致排查方向错误。
  • DASHBOARD_USERNAMEDASHBOARD_PASSWORD。这是 Studio 的基本身份验证凭据。默认密码实际为 this_password_is_insecure_and_should_be_updated

请理解为什么不能随意生成 ANON_KEYSERVICE_ROLE_KEY。二者都是使用 JWT_SECRET 签名的 JWT。网关会验证每个请求的签名,因此与您的密钥不匹配的密钥会因 {"message":"Invalid authentication credentials"} 而被拒绝。这是自托管最常见的故障:管理员修改了 JWT_SECRET,却保留了演示密钥。请始终同时生成这三个值。

请将 SERVICE_ROLE_KEY 按 root 密码的安全级别进行保护。它会完全绕过行级安全策略。只能将它放在服务器端代码中,不能放在其他位置。

SITE_URLAPI_EXTERNAL_URL 设置为用户实际访问的地址,例如 https://supabase.example.com。Auth 会根据这些值生成电子邮件确认链接和 OAuth 回调链接。如果保留为 http://localhost:8000,所有用户都会被重定向到各自的本机。

然后检查当前配置:

sh run.sh secrets

启动并确认服务运行正常

sh run.sh start
docker compose ps

run.sh start 会封装 docker compose up -d --wait,因此只有健康检查通过后才会返回。每个服务都应显示 running (healthy)running。首次启动需要 2 到 4 分钟,因为 Postgres 会先运行初始化脚本,之后其他服务才能连接。

如果容器不断重启,请按服务名称查看其日志:

docker compose logs db
docker compose logs auth

Studio 随后会监听 8000 端口,并要求输入您设置的仪表板用户名和密码。

不要将端口 8000 暴露到公网

Kong 在 8000 端口上提供的是明文 HTTP。每个 API 密钥和用户密码都会以明文形式通过网络传输,Studio 凭据使用的是基本身份验证,即 Base64 编码,而不是加密。

在前面放置反向代理,在那里终止 TLS(传输层安全),并将 Kong 绑定到回环地址,使其他任何进程都无法访问它。在 docker-compose.yml 中,kong 端口映射改为 127.0.0.1:8000:8000,代理再将请求转发到该端口。在多个 Compose 应用前使用 Traefik介绍了证书配置。该代理最终还会为主机上的其他服务提供统一入口,从当前堆栈到类似将 Jellyfin 媒体库重建为 90 年代录像店这类非严肃项目;这些服务都应使用主机名,而不是再开放一个端口。如果某个仪表板始终只有您自己使用,可以跳过代理,改为通过 SSH 隧道访问回环端口;self-hosted open-kritt也采用了相同方法,使其扫描界面完全不暴露到公网。

还应在防火墙中关闭其余端口,因为 Docker 会通过写入自己的 iptables 规则来发布端口,简单的 ufw 配置无法拦截这些规则。为什么 Docker 容器会忽略您的 ufw 规则解释了这一问题。

备份数据库,而不是目录

Postgres 数据存储在 ./volumes/db/data 的 bind mount 中。容器运行时复制该目录会得到不完整的副本,因为 Postgres 会缓冲写入,磁盘上的文件只有在检查点时才处于一致状态。恢复该副本通常可以成功,但有时会悄悄丢失最后几笔事务。这是备份最糟糕的失败方式。

改用转储。pg_dumpall 在容器内运行,并生成一致的快照:

docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sql

确认文件非空后再信任它。然后按计划将这些转储发送到服务器之外,这正是 使用 restic 加密备份到异地 的用途。静默失败的定时转储与完全没有备份无法区分,因此应让 cron 或 systemd 任务在退出码非零时 向您的手机发送告警。同时备份您的 .env。丢失 JWT_SECRET 会使所有已签发的令牌失效,并使所有存储的加密密钥无法读取。

上传的文件位于 ./volumes/storage 中,这些是普通文件,因此直接复制即可。

无数据丢失地更新

Supabase 在 docker-compose.yml 中固定镜像版本,因此除非您主动更新,否则不会发生任何变化。无论您手动组装哪种技术栈,都值得采用版本固定方式。正因如此,自托管的 RustDesk 中继固定了两个服务器镜像,而不是跟随会移动的标签:升级应由您选择合适的时间执行。每次升级前都先创建转储。

docker compose pull
sh run.sh recreate

recreate 会停止技术栈,然后使用新镜像重新启动。您的数据不会丢失,因为数据位于主机上的绑定挂载中,而不在容器内部。执行重大版本升级前,请先阅读仓库中的 CHANGELOG.md,因为 Postgres 主版本升级不会自动完成,需要先创建转储,再执行恢复。

如果要应用 Compose 文件本身的更改,请再次克隆上游仓库,并将其中的 docker 目录复制到您的项目中,同时注意不要覆盖 .env

完整重置会销毁包括数据库在内的所有内容。它使用单独的脚本,并会要求确认:

sh reset.sh

FAQ

为什么我的 API 调用返回“Invalid authentication credentials”?

您的 ANON_KEYSERVICE_ROLE_KEY 没有使用当前位于 .env 中的 JWT_SECRET 进行签名。网关会验证每个请求的签名,签名不匹配时将拒绝请求。使用 sh utils/generate-keys.sh --update-env 重新生成这三个值,然后运行 sh run.sh recreate,让服务读取新值。

我可以在 2 GB VPS 上运行自托管 Supabase 吗?

不可靠。截至 July 2026,该技术栈空闲时占用约 3 GB,因为它会运行约十四个服务。因此,2 GB 服务器上的容器会被 out of memory killer 终止,您会在 docker compose ps 中看到退出代码 137。生产环境使用 8 GB;个人开发至少使用 4 GB。

自托管 Supabase 是否包含 edge functions?

包含。Compose 文件包含基于 Deno 的函数运行时,并会提供您放置在 ./volumes/functions 下的所有内容。它不包含托管平台的全球部署网络,因此您的函数只会在一台服务器上的一个位置运行。

如何直接连接到 Postgres 数据库?

在服务器本机上使用 docker exec -it supabase-db psql -U postgres 打开交互式 shell。对于外部客户端,请通过 5432 端口上的 Supavisor 连接,并使用用户 postgres.<POOLER_TENANT_ID> 和您的 POSTGRES_PASSWORD。不要将该端口暴露到互联网。请通过 VPN 或 SSH 隧道访问。

为什么我的身份验证确认邮件链接到了 localhost?

.env 中的 SITE_URLAPI_EXTERNAL_URL 仍为默认值。身份验证服务会根据这两个值生成所有确认链接和密码重置链接,因此会发送配置中的地址。将两者都设置为真实的公网 URL,然后重新创建技术栈。