Supabase 本地开发环境搭建:一行命令跑起来

概述

Supabase 被称为开源版 Firebase。它提供 PostgreSQL、认证、实时推送、对象存储等完整能力。

本地跑一套 Supabase,意味着你不需要依赖任何云服务就能进行全栈开发。

今天教你用 Docker Compose 一键启动,全程不超过 5 分钟。

准备工作

你需要:

  • 已安装 Docker 和 Docker Compose
  • 至少 2GB 可用内存
  • 一个空项目目录

检查 Docker 是否就绪:

docker --version
docker compose version

第一步:创建项目目录

mkdir -p ~/supabase-dev && cd ~/supabase-dev

第二步:编写 docker-compose.yml

services:
  studio:
    image: supabase/studio:latest
    container_name: supabase-studio
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      STUDIO_PG_META_URL: http://meta:8080
      POSTGRES_PASSWORD: postgres
      ORGANISATION_NAME: local
      DEFAULT_PROJECT_ID: supabase-local
      NEXT_PUBLIC_ENABLE_LOGGING: "false"
      LOGGOVERNMENT_URL: http://logflare:4000
      NEXT_PUBLIC_ANALYTICS_ENABLED: "false"
      NEXT_SUPABASE_URL: http://localhost:9999
      NEXT_SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODY4NzU5MTl9.CRQ1gWJpOyMbXUbJxlVhkWqQj3nE7R6y0V3V2V6V3V6

  db:
    image: supabase/postgres:15.1.0.84
    container_name: supabase-db
    restart: unless-stopped
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_HOST: /var/run/postgresql
      API_JWT_SECRET: your-jwt-secret-change-in-production
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
      FUNCTIONS_REDIRECT_URL: http://localhost:9999
      PGBIN: /usr/lib/postgresql/15/bin
      PGDATA: /var/lib/postgresql/data
      JWT_EXP: 3600
    volumes:
      - supabase_db_data:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - supabase

  auth:
    image: supabase/gotrue:v2.149.0
    container_name: supabase-auth
    restart: unless-stopped
    ports:
      - "9999:9999"
    environment:
      API_HOST: 0.0.0.0
      API_PORT: 9999
      DB_DRIVER: postgres
      DB_DATABASE: postgres
      DB_HOST: db
      DB_USER: postgres
      DB_PASSWORD: postgres
      JWT_SECRET: your-jwt-secret-change-in-production
      SITE_URL: http://localhost:3000
      CORS_ALLOWED_ORIGINS: "*"
      MAILER_AUTOCONFIRM: "true"
      DISABLE_SIGNUP: "false"
      GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
      GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: "false"
      GOTRUE_SMTP_MAX_FREQUENCY: 1s
    depends_on:
      db:
        condition: service_healthy
    networks:
      - supabase

  storage:
    image: supabase/storage-api:latest
    container_name: supabase-storage
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      API_HOST: 0.0.0.0
      API_PORT: 5000
      DB_DRIVER: postgres
      DB_DATABASE: postgres
      DB_HOST: db
      DB_USER: postgres
      DB_PASSWORD: postgres
      ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODY4NzU5MTl9.CRQ1gWJpOyMbXUbJxlVhkWqQj3nE7R6y0V3V2V6V3V6
      SERVICE_ROLE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4Njg3NTkxOX0.EgBZ5V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6
      DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
      POSTGREST_URL: http://postgrest:3000
      PGRST_JWT_SECRET: your-jwt-secret-change-in-production
      REGION: us-east-1
      TENANT_ID: static
      GLOBAL_S3_BUCKET: storage
      ENABLE_FILE_UPLOAD: "true"
      ENABLE_IMAGE_TRANSFORMATION: "true"
      LARGE_OBJECT_MAX_COUNT: 10000
    depends_on:
      db:
        condition: service_healthy
    networks:
      - supabase

  postgrest:
    image: postgrest/postgrest:v12.2.0
    container_name: supabase-rest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      PGRST_DB_SCHEMA: public
      PGRST_DB_ANON_ROLE: anon
      PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
      PGRST_JWT_SECRET: your-jwt-secret-change-in-production
      PGRST_DB_USE_LEGACY_GUCS: "false"
      PGRST_LOG_LEVEL: warn
      PGRST_BODY_LIMIT: 10mb
    depends_on:
      db:
        condition: service_healthy
    networks:
      - supabase

  realtime:
    image: supabase/realtime:v2.31.14
    container_name: supabase-realtime
    restart: unless-stopped
    ports:
      - "4000:4000"
    environment:
      API_JWT_SECRET: your-jwt-secret-change-in-production
      DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASSWORD: postgres
      DB_NAME: postgres
      API_HOST: 0.0.0.0
      PORT: 4000
      SECURE_CHANSOCK: "false"
      POSTGRES_BACKEND_URL: postgres://postgres:postgres@db:5432/postgres
      ERL_AFLAGS: -proto_dist inet_tcp
      ENABLE_TAILSCALE: "false"
      DNS_NODES: ""
      SECRET_KEY_BASE: uppxqfb6ixldj7no3txbya-awnessent-7hg3k4a8
      VOYAGER_ENDPOINT: http://postgrest:3000
    depends_on:
      db:
        condition: service_healthy
    networks:
      - supabase

  meta:
    image: supabase/postgres-meta:v0.77.0
    container_name: supabase-meta
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      PG_META_PORT: 8080
      PG_META_DB_HOST: db
      PG_META_DB_PORT: 5432
      PG_META_DB_NAME: postgres
      PG_META_DB_USER: postgres
      PG_META_DB_PASSWORD: postgres
    depends_on:
      db:
        condition: service_healthy
    networks:
      - supabase

  logflare:
    image: supabase/logflare:1.11.0
    container_name: supabase-logflare
    restart: unless-stopped
    ports:
      - "4001:4000"
    environment:
      LOGFLARE_SINGLE_NODE: "true"
      LOGFLARE_HOST: localhost:4001
      LOGFLARE_LOG_LEVEL: info
      LOGFLARE_FEATURE_FLAGS: "multibackend"
      LOGFLARE_API_KEY: your-api-key-change-in-production
    networks:
      - supabase

  functions:
    image: supabase/edge-runtime:v1.55.0
    container_name: supabase-edge-functions
    restart: unless-stopped
    ports:
      - "9999:9000"
    environment:
      VERCEL_TOKEN: ""
      SUPABASE_URL: http://host.docker.internal:8080
      SUPABASE_DB_URL: postgres://postgres:postgres@db:5432/postgres
      SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODY4NzU5MTl9.CRQ1gWJpOyMbXUbJxlVhkWqQj3nE7R6y0V3V2V6V3V6
      SUPABASE_SERVICE_ROLE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4Njg3NTkxOX0.EgBZ5V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6V6
    networks:
      - supabase

volumes:
  supabase_db_data:

networks:
  supabase:
    driver: bridge

上面的 JWT 密钥和 API Key 是 Supabase 官方 demo 值,本地开发足够。实际生产环境请替换为随机生成的密钥。

第三步:创建初始化目录

mkdir -p init functions

init/ 目录存放数据库初始化 SQL 文件,functions/ 目录存放 Edge Functions 代码。

第四步:启动服务

docker compose up -d

首次启动需要下载多个镜像,大约等待 1-3 分钟。

检查所有容器状态:

docker compose ps

你应该看到 7 个服务全部处于 running 状态。

第五步:验证服务

1. 检查数据库连接

docker compose exec db psql -U postgres -c "SELECT version();"

应返回 PostgreSQL 版本信息。

2. 测试 REST API

curl http://localhost:3000

应返回 SupaUtils health check 响应。

3. 测试认证接口

curl -X POST http://localhost:9999/auth/v1/signup \
  -H "Content-Type: application/json" \
  -H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODY4NzU5MTl9.CRQ1gWJpOyMbXUbJxlVhkWqQj3nE7R6y0V3V2V6V3V6" \
  -d '{"email":"test@example.com","password":"test123456"}'

4. 访问 Studio 管理界面

浏览器打开:

http://localhost:3000

默认账号:

  • 邮箱:test@example.com
  • 密码:test123456

或者访问项目 Dashboard:

http://localhost:3000/project/default

5. 检查所有端口

netstat -tlnp | grep -E '5432|3000|4000|5000|8080|4001|9999'

服务端口速查

服务端口用途
Studio UI3000管理界面
PostgreSQL5432数据库直连
REST API3000PostgREST
Realtime4000WebSocket 实时推送
Storage5000对象存储 API
Meta API8080数据库元数据
Logflare4001日志查看
Edge Functions9999边缘函数
Auth9999认证服务

常见问题

启动后 Studio 打不开

先等 30 秒让数据库完全就绪,然后刷新页面。

检查日志:

docker compose logs db | tail -20

连接数据库失败

确保 PostgreSQL 健康检查通过:

docker compose exec db pg_isready

想删除所有数据重新开始

docker compose down -v
docker compose up -d

删除 -v 则保留数据卷。

内存不足导致容器启动失败

Supabase 本地全套服务大约占用 500MB-1GB 内存。如果 VPS 内存小于 2GB,可以只启动核心服务:

# 只启动数据库和 REST
docker compose up -d db postgrest meta

修改数据库 Schema

在 init/ 目录下放入 .sql 文件,容器首次启动时会自动执行。例如创建 users 表:

cat > init/001-create-users.sql << 'EOF'
CREATE TABLE users (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

GRANT SELECT ON users TO anon, authenticated;
EOF

进阶:集成到现有项目

在 Next.js 项目中,使用官方 SDK 连接本地 Supabase:

npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'

const supabase = createClient('http://localhost:9999', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODY4NzU5MTl9.CRQ1gWJpOyMbXUbJxlVhkWqQj3nE7R6y0V3V2V6V3V6')

本地开发完,切换到生产 Supabase 只需要改这两行配置。

总结

一行 docker compose up -d,完整的 Supabase 开发环境就跑起来了。

PostgreSQL、认证、存储、实时推送全部就位,本地开发不再依赖任何第三方服务。

下次需要切换项目?删掉数据卷重来即可,干净利落。

你的本地 Supabase 用起来了么?遇到什么问题可以在评论区留言。

这篇文章对你有帮助吗?