如何用 Docker 在 VPS 上自托管 Supabase
在自有 VPS 上运行官方 Supabase Docker Compose 堆栈,了解必须替换的默认密钥、11 个服务的用途、实际内存需求,以及备份和更新方法。
构建内容
自行托管 Supabase,意味着在自己的服务器上运行官方 Docker Compose 堆栈:Postgres、前置 REST API、身份验证服务、文件存储、实时 WebSocket,以及 Studio 控制面板。您只需克隆一个仓库、编辑一个 .env 文件,然后启动 11 个容器。这些容器共同提供一个由您控制的 Supabase 项目。
安装过程很短。最容易出问题的是 .env 文件。该文件自带仓库中公开的演示密钥,使用这些默认值启动的堆栈对任何发现它的人开放。本指南介绍必须替换的密钥、各服务的用途、该堆栈实际需要多少内存,以及如何在不删除数据库的情况下更新它。
如果您不熟悉 Compose,请先阅读 VPS 上的 Docker Compose 基础知识。以下内容假设 docker compose version 已能输出版本号。
实际包含哪些服务
Supabase 不是单个程序。Compose 文件会在同一个网络中启动一组独立服务。了解每个服务的作用后,面对一长串容器名称时,才能进行排查。
db是加载了 Supabase 扩展的 PostgreSQL。其他所有服务都会与它通信。如果此容器运行不正常,其他服务也会全部失败。api-gw是 API 网关。自 August 2026 版本起,它使用 Envoy;在此之前使用名为kong的服务。Kong 仍可通过可选覆盖文件启用。它监听 8000 端口,并将/rest/v1/、/auth/v1/和/storage/v1/路由到正确的后端。只有这个容器应对外暴露。rest是 PostgREST。它读取 Postgres 架构并将其作为 REST API 提供,因此新建表后无需编写代码就会产生新的端点。auth是 GoTrue。它签发用于标识用户的 JSON Web Token(JWT)。storage和imgproxy负责文件上传和图像缩放。realtime通过 WebSocket 流式传输数据库变更。studio和meta分别是控制面板及其背后的管理 API。functions是基于 Deno 的边缘运行时,supavisor是 Postgres 连接池器。
日志和分析服务(Logflare 加 vector)已于 June 2026 从默认 Compose 文件中移除。现在它们位于可选覆盖文件中。使用 sh run.sh config add logs 启用该文件。Compose 文件的这种分层方式与使用多个 Compose 文件运行一个服务栈中介绍的方式相同。
这就是下面的资源数量如此设置的原因。你运行的不只是一个数据库,而是一个数据库加上十个支持服务。
规划 8 GB 内存
截至 2026 年 7 月,在全新安装且尚未加入您自己的数据或流量时,该堆栈空闲状态下占用约 2.5 到 3 GB 常驻内存。其中,Analytics 服务和 Studio 的 Node.js 进程是两个占用内存最多的单独进程。Analytics 已不再属于默认堆栈,因此只有启用日志覆盖配置时才会占用内存。2 GB 服务器可以启动这些容器,但随后其中一个会被内核的内存不足终止机制杀掉。表现为容器不断重启,并以退出代码 137 退出。Supabase 自托管文档于 2026 年 10 月核查后列出的最低配置是 4 GB RAM、2 个 CPU 核心和 40 GB SSD;推荐配置是 8 GB RAM、4 个核心和 80 GB 存储。
对于任何依赖该服务的环境,请配置 8 GB RAM 和 4 个 vCPU。如果您接受繁重查询和 Studio 会话同时运行时速度较慢,4 GB 可用于个人开发实例。磁盘同样重要,因为 Postgres、存储卷以及启用日志后的日志数据都会位于项目目录下。先从 40 GB 开始,并持续监控。选择方案前统计服务数量,是自托管任何应用都值得保持的习惯,因为 PhotoPrism 和 Immich 的实际内存下限明显高于其快速入门页面所暗示的配置。如果当前项目还不需要这么高的服务器配置,请将其与 Supabase 免费云计划实际包含的内容 进行比较,因为一个内存占用低于 500 MB 且一周内从不闲置的小型应用,可能可以零成本运行在其中。
安装:克隆官方仓库
受支持的方式是将主仓库中的 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 pulldocker compose pull 会下载数 GB 的镜像。命令结束时,每个服务都应标记为 Pulled。此处出现 manifest unknown 错误,表示上游已删除固定的镜像标签。解决方法是重新拉取仓库的较新副本,而不是手动修改标签。
首次启动前必须更改的密钥
请在启动堆栈前完成这些操作,不要等到启动后再做。其中几个值会在首次启动时写入数据,因此之后再修改就意味着重置数据库。
仓库提供了一个生成器,可以正确生成所有值,包括必须使用新的 JWT 密钥签名的两个 API 密钥。
sh utils/generate-keys.sh --update-env该脚本会将 JWT_SECRET、ANON_KEY、SERVICE_ROLE_KEY、SECRET_KEY_BASE、REALTIME_DB_ENC_KEY、VAULT_ENC_KEY、PG_META_CRYPTO_KEY、Logflare 令牌、S3 协议密钥和 MINIO_ROOT_PASSWORD 写入 .env。截至 October 2026,它还会为 POSTGRES_PASSWORD 和 DASHBOARD_PASSWORD 写入随机值。该脚本需要 openssl,所有正常的 Ubuntu 镜像都包含此工具。由于脚本现在也会重写数据库密码,因此只能在首次启动前使用 --update-env 运行,绝不能对已经包含数据的堆栈运行。
以下两个值仍值得在 .env 中手动检查:
POSTGRES_PASSWORD。脚本会为它填入随机十六进制字符串,这样是安全的。如果改用自定义值,只能使用字母和数字。此处的标点符号会破坏多个服务通过拼接字符串生成的连接字符串,导致错误表现为身份验证错误,而不是解析错误,容易使排查方向出现偏差。DASHBOARD_USERNAME和DASHBOARD_PASSWORD。这两个值是 Studio 的基本身份验证凭据。脚本会设置随机密码,但会将用户名保留为默认值supabase。随附的默认密码就是this_password_is_insecure_and_should_be_updated;如果跳过了脚本,请立即修改该密码。
请理解为什么不能自行编造 ANON_KEY 和 SERVICE_ROLE_KEY。两者都是使用 JWT_SECRET 签名的 JWT。网关只接受 .env 中的确切密钥,其后端服务会根据 JWT_SECRET 验证签名,因此不匹配的密钥会被拒绝,并返回 HTTP 401。在默认的 Envoy 网关上,响应正文是普通的 Unauthorized;在较旧的安装以及 Kong 覆盖配置中,Kong 会返回 {"message":"Invalid authentication credentials"}。这是自托管环境中最常见的故障:操作员修改了 JWT_SECRET,却保留了演示密钥。务必同时生成这三个值。
将 SERVICE_ROLE_KEY 视为 root 密码。它可以完全绕过行级安全策略。它只能放在服务器端代码中,不能出现在其他位置。
将 SITE_URL 设置为应用的地址,将 API_EXTERNAL_URL 设置为 API 的公网地址,并在末尾加上 /auth/v1,例如 https://supabase.example.com/auth/v1。将 SUPABASE_PUBLIC_URL 设置为不带路径的同一公网地址。Auth 会根据这些值生成电子邮件确认链接和 OAuth 回调链接,因此如果保留 localhost 默认值,你的所有用户都会被重定向到各自的本机。
然后检查当前配置:
sh run.sh secrets启动并确认服务运行正常
sh run.sh start
docker compose psrun.sh start 会封装 docker compose up -d --wait,因此只有健康检查通过后才会返回。每个服务都应显示 running (healthy) 或 running。首次启动需要 2 到 4 分钟,因为 Postgres 会先运行初始化脚本,之后其他服务才能连接。
如果容器不断重启,请按服务名称查看其日志:
docker compose logs db
docker compose logs authStudio 随后会监听 8000 端口,并要求输入您设置的仪表板用户名和密码。
不要将端口 8000 暴露到公网
8000 端口上的网关使用明文 HTTP。每个 API 密钥和用户密码都会以明文在网络中传输;Studio 凭据使用基本身份验证,其本质是 Base64 编码,而不是加密。
在网关前配置反向代理,并在代理层终止 TLS(传输层安全)。同时将网关绑定到回环地址,使其他主机无法访问它。在 docker-compose.yml 中,api-gw 服务下的端口配置改为 127.0.0.1:${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp,然后让代理转发到该端口。在 2026 年 8 月切换到 Envoy 之前安装的版本中,同一行端口配置位于名为 kong 的服务下。在多个 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 recreaterecreate 会停止技术栈,然后使用新镜像重新启动。数据仍然存在,因为数据位于主机上的绑定挂载中,而不是容器内部。执行大版本升级前,请先阅读仓库中的 CHANGELOG.md,因为 Postgres 大版本升级不会自动执行,需要先转储再恢复。
如果要获取 Compose 文件本身的变更,请在项目目录中运行 sh update.sh;也可以先运行 sh update.sh --dry-run 查看将发生哪些变化。它会将上游文件与您自己的修改合并,绝不会覆盖 .env 中已有的值(只会追加新键),并会在写入前将配置归档到 backups/,但不会归档数据库。没有 .supabase-version 文件的安装需要使用 --from 以及最初使用的版本。对于 August 2026 版本,这一点尤其重要:该版本将 kong 服务替换为 api-gw,并要求更新 docker-compose.yml。手动方式仍然可用:重新克隆上游仓库,并将其中的 docker 目录复制到项目中,同时注意不要覆盖 .env。
完整重置会删除包括数据库在内的所有内容,并将 .env 恢复为默认值(旧文件会保留为 .env.old)。这是一个独立脚本,运行时会要求确认:
sh reset.shFAQ
为什么我的 API 调用返回“身份验证凭据无效”?
这条确切消息来自 Kong。对于 2026 年 8 月之前安装的实例,或使用 Kong 覆盖配置的实例,网关是 Kong。默认的 Envoy 网关会将同一问题报告为普通的 401 Unauthorized。无论哪种情况,您发送的密钥都与 .env 中的 ANON_KEY 或 SERVICE_ROLE_KEY 不匹配,或者这些密钥不是使用 .env 中当前的 JWT_SECRET 签名的。在从未存储过数据的堆栈上,使用 sh utils/generate-keys.sh --update-env 同时重新生成这 3 个值。在运行中的堆栈上,不带该标志运行脚本,并回答 no,使脚本只输出新值;手动将 JWT_SECRET、ANON_KEY 和 SERVICE_ROLE_KEY 复制到 .env 中,并保持 POSTGRES_PASSWORD 不变。然后运行 sh run.sh recreate,使服务读取新值。
我可以在 2 GB VPS 上运行自托管 Supabase 吗?
不可靠。截至 2026 年 10 月,Supabase 的自托管文档将 4 GB RAM 列为最低要求;截至 2026 年 7 月,包含日志服务的测量结果显示,该堆栈空闲时接近 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_URL 和 API_EXTERNAL_URL 仍使用默认值。身份验证服务会根据这两个值生成所有确认链接和密码重置链接,因此会发送配置中指定的地址。将两者都设置为真实的公网 URL,然后重新创建堆栈。