supago

package module
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2025 License: MIT Imports: 26 Imported by: 0

README ΒΆ

SupaGo: A Go/Supabase Utility

A lightweight Go utility to help integrate Supabase into self-hosted Go servers and projects.
This project is based on the official Supabase Docker Compose guide, with a curated subset of services tailored for Go environments.


πŸš€ Features

  • Provides a self-hosted Supabase stack you can spin up with Docker Compose.
  • Designed to be integrated into Go servers/projects without extra dependencies.
  • Includes Supabase core services (Auth, REST, Realtime, Storage, Studio, etc.).
  • Excludes unsupported services for now:
    • ❌ Supavisor (connection pooler)
    • ❌ Edge Functions
    • ❌ Vector (observability)

πŸ“¦ Services & Versions

The following services (with pinned versions) are included:

Service Image / Version
Studio supabase/studio:2025.06.30-sha-6f5982d
Kong kong:2.8.1
Auth (GoTrue) supabase/gotrue:v2.177.0
REST (PostgREST) postgrest/postgrest:v12.2.12
Realtime supabase/realtime:v2.34.47
Storage supabase/storage-api:v1.25.7
Imgproxy darthsim/imgproxy:v3.8.0
Meta (Postgres Meta) supabase/postgres-meta:v0.91.0
Analytics (Logflare) supabase/logflare:1.14.2
Database supabase/postgres:15.8.1.060

Unsupported (for now):

  • Supavisor β†’ supabase/supavisor:2.5.7
  • Functions β†’ supabase/edge-runtime:v1.69.6
  • Vector β†’ timberio/vector:0.28.1-alpine

πŸ› οΈ Usage

Requires docker on the local machine

Directly integrate SupaGo into your freestanding project!

Add the repository:

go get github.com/train360-corp/supago

Run a basic Supabase instance:

package main

import (
  "context"
  "github.com/train360-corp/supago"
  "go.uber.org/zap/zapcore"
  "os/signal"
  "syscall"
)

// encryptionKey is used for SupaBase Vault (must be persisted between restarts)
// Should not be inlined like below, and should be stored/retrieved more securely; consider:
// - supago.EncryptionKeyFromFile (generates a secret from a filepath and reads therefrom)
var encryptionKey = supago.StaticEncryptionKey("d9bf2393c65c006cc83625f85a27cc50882a391b1e0ab4fd4c2535dbe1f8a283")

// use any zap logger of choice, customized for specific use-case, or even disable logging altogether:
// var logger = zap.NewNop().Sugar() // No-Op / non-operational logger
var logger = supago.NewOpinionatedLogger(zapcore.InfoLevel, false)

func main() {

  ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
  defer cancel()

  logger.Infof("SupaGo starting")

  // build a config
  cfg := supago.ConfigBuilder().
    Platform("example-project").
    GetEncryptionKeyUsing(encryptionKey).
    Build()

  // create a new SupaGo instance
  // this example uses all (supported) services; alternatively, add individual services as needed; e.g.:
  // AddService(supago.Services.Postgres, supago.Services.Kong)
  sg := supago.New(cfg).
    SetLogger(logger).
    AddServices(supago.Services.All)

  // run services
  if err := sg.RunForcefully(ctx); err != nil {
    logger.Errorf("an error occured while running services: %v", err)
  }

  // TODO: run custom backend services, interact with supabase, etc.

  <-ctx.Done() // block until done

  logger.Warnf("stop-signal recieved")

  sg.Stop() // shutdown services
}

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func ConfigBuilder ΒΆ added in v1.6.0

func ConfigBuilder() *configBuilder

func IsValidEncryptionKey ΒΆ added in v1.4.1

func IsValidEncryptionKey(key string) (bool, error)

func IsValidPlatformName ΒΆ

func IsValidPlatformName(platform string) bool

func NewOpinionatedLogger ΒΆ

func NewOpinionatedLogger(LogLevel zapcore.Level, LogJsonFmt bool) *zap.SugaredLogger

NewOpinionatedLogger creates a new logger (stylistically opinionated)

Types ΒΆ

type Config ΒΆ

type Config struct {
	Global    GlobalConfig
	Database  DatabaseConfig
	Storage   StorageConfig
	Dashboard DashboardConfig
	LogFlare  LogFlareConfig
	Keys      KeysConfig
	Kong      KongConfig
}

type DashboardConfig ΒΆ

type DashboardConfig struct {
	Username string
	Password string
}

type DatabaseConfig ΒΆ

type DatabaseConfig struct {
	DataDirectory string
	Password      string
}

type EmbeddedFile ΒΆ

type EmbeddedFile struct {
	Data []byte
	Path string
}

type EncryptionKeyGetter ΒΆ added in v1.4.0

type EncryptionKeyGetter func() (string, error)

type GetEncryptionKeyFrom ΒΆ added in v1.4.0

type GetEncryptionKeyFrom[T any] func(T) EncryptionKeyGetter
var EncryptionKeyFromConfig GetEncryptionKeyFrom[Config] = func(config Config) EncryptionKeyGetter {

	return EncryptionKeyFromFile(filepath.Join(filepath.Dir(config.Database.DataDirectory), "pgsodium_root.key"))
}

EncryptionKeyFromConfig construct an encryption key from a Config

var EncryptionKeyFromFile GetEncryptionKeyFrom[string] = func(path string) EncryptionKeyGetter {
	return func() (string, error) {
		if info, err := os.Stat(path); err != nil {
			if os.IsNotExist(err) {

				if err := createEncryptionKeyFile(path); err != nil {
					return "", err
				}

				return readEncryptionKeyFile(path)
			} else {
				return "", fmt.Errorf("error checking postgres pgsodium key file \"%s\" exists: %v", path, err)
			}
		} else if info.IsDir() {
			return "", fmt.Errorf("postgres pgsodium key file \"%s\" exists but is not a file", path)
		} else {

			return readEncryptionKeyFile(path)
		}
	}
}

EncryptionKeyFromFile retrieve an encryption key from a file (at `path`) If `path` does not exist, EncryptionKeyFromFile will attempt to create it

var StaticEncryptionKey GetEncryptionKeyFrom[string] = func(key string) EncryptionKeyGetter {
	return func() (string, error) {
		return key, nil
	}
}

StaticEncryptionKey pass a static (or self-retrieved, from external means/methods) encryption key

type GlobalConfig ΒΆ

type GlobalConfig struct {
	PlatformName string
	DebugMode    bool
}

type KeysConfig ΒΆ

type KeysConfig struct {
	JwtSecret          string
	PublicJwt          string
	PrivateJwt         string
	PgSodiumEncryption string
}

type KongConfig ΒΆ

type KongConfig struct {
	URLs KongURLsConfig
	SMTP KongSMTPConfig
}

type KongSMTPConfig ΒΆ

type KongSMTPConfig struct {
	Host string
	Port uint16
	User string
	Pass string
	From KongSMTPFromConfig
}

type KongSMTPFromConfig ΒΆ

type KongSMTPFromConfig struct {
	Email string
	Name  string
}

type KongURLsConfig ΒΆ

type KongURLsConfig struct {
	Site string // where the frontend Site is publicly accessible
	Kong string // where Kong is publicly accessible
}

type LogFlareConfig ΒΆ

type LogFlareConfig struct {
	PrivateKey string
	PublicKey  string
}

type PreBuiltServices ΒΆ added in v1.4.0

type PreBuiltServices struct {
	Analytics ServiceConstructor
	Auth      ServiceConstructor
	ImgProxy  ServiceConstructor
	Kong      ServiceConstructor
	Meta      ServiceConstructor
	Postgres  ServiceConstructor
	Postgrest ServiceConstructor
	Realtime  ServiceConstructor
	Storage   ServiceConstructor
	Studio    ServiceConstructor
}
var Services PreBuiltServices = PreBuiltServices{

	Analytics: func(config Config) Service {
		return Service{
			Image:   "supabase/logflare:1.14.2",
			Name:    containerName(config, "supago-analytics"),
			Aliases: []string{"analytics"},
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"curl",
					"http://localhost:4000/health",
				},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  10,
			},
			Env: []string{
				fmt.Sprintf("%s=%s", "LOGFLARE_NODE_HOST", "127.0.0.1"),
				fmt.Sprintf("%s=%s", "DB_USERNAME", "supabase_admin"),
				fmt.Sprintf("%s=%s", "DB_DATABASE", "_supabase"),
				fmt.Sprintf("%s=%s", "DB_HOSTNAME", containerName(config, dbContainerName)),
				fmt.Sprintf("%s=%s", "DB_PORT", "5432"),
				fmt.Sprintf("%s=%s", "DB_PASSWORD", config.Database.Password),
				fmt.Sprintf("%s=%s", "DB_SCHEMA", "_analytics"),
				fmt.Sprintf("%s=%s", "LOGFLARE_PUBLIC_ACCESS_TOKEN", config.LogFlare.PublicKey),
				fmt.Sprintf("%s=%s", "LOGFLARE_PRIVATE_ACCESS_TOKEN", config.LogFlare.PrivateKey),
				fmt.Sprintf("%s=%s", "LOGFLARE_SINGLE_TENANT", "true"),
				fmt.Sprintf("%s=%s", "LOGFLARE_SUPABASE_MODE", "true"),
				fmt.Sprintf("%s=%s", "LOGFLARE_MIN_CLUSTER_SIZE", "1"),
				fmt.Sprintf("%s=%s", "POSTGRES_BACKEND_URL",
					fmt.Sprintf("postgresql://supabase_admin:%s@%s:5432/_supabase", config.Database.Password, containerName(config, dbContainerName))),
				fmt.Sprintf("%s=%s", "POSTGRES_BACKEND_SCHEMA", "_analytics"),
				fmt.Sprintf("%s=%s", "LOGFLARE_FEATURE_FLAG_OVERRIDE", "multibackend=true"),
			},
		}
	},

	Auth: func(config Config) Service {
		return Service{
			Name:    containerName(config, "supago-auth"),
			Aliases: []string{"auth", "gotrue"},
			Image:   "supabase/gotrue:v2.177.0",
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"wget",
					"--no-verbose",
					"--tries=1",
					"--spider",
					"http://localhost:9999/health",
				},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  3,
			},
			Env: []string{
				fmt.Sprintf("%s=%s", "GOTRUE_API_HOST", "0.0.0.0"),
				fmt.Sprintf("%s=%s", "GOTRUE_API_PORT", "9999"),
				fmt.Sprintf("%s=%s", "API_EXTERNAL_URL", config.Kong.URLs.Kong),

				fmt.Sprintf("%s=%s", "GOTRUE_DB_DRIVER", "postgres"),
				fmt.Sprintf("%s=%s", "GOTRUE_DB_DATABASE_URL",
					fmt.Sprintf("postgres://supabase_auth_admin:%s@%s:5432/postgres", config.Database.Password, containerName(config, dbContainerName))),

				fmt.Sprintf("%s=%s", "GOTRUE_SITE_URL", config.Kong.URLs.Site),
				fmt.Sprintf("%s=%s", "GOTRUE_URI_ALLOW_LIST", ""),
				fmt.Sprintf("%s=%s", "GOTRUE_DISABLE_SIGNUP", "false"),

				fmt.Sprintf("%s=%s", "GOTRUE_JWT_ADMIN_ROLES", "service_role"),
				fmt.Sprintf("%s=%s", "GOTRUE_JWT_AUD", "authenticated"),
				fmt.Sprintf("%s=%s", "GOTRUE_JWT_DEFAULT_GROUP_NAME", "authenticated"),
				fmt.Sprintf("%s=%s", "GOTRUE_JWT_EXP", "3600"),
				fmt.Sprintf("%s=%s", "GOTRUE_JWT_SECRET", config.Keys.JwtSecret),

				fmt.Sprintf("%s=%s", "GOTRUE_EXTERNAL_EMAIL_ENABLED", "true"),
				fmt.Sprintf("%s=%s", "GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED", "false"),
				fmt.Sprintf("%s=%s", "GOTRUE_MAILER_AUTOCONFIRM", "false"),
				fmt.Sprintf("%s=%s", "GOTRUE_SMTP_ADMIN_EMAIL", config.Kong.SMTP.From.Email),
				fmt.Sprintf("%s=%s", "GOTRUE_SMTP_HOST", config.Kong.SMTP.Host),
				fmt.Sprintf("%s=%d", "GOTRUE_SMTP_PORT", config.Kong.SMTP.Port),
				fmt.Sprintf("%s=%s", "GOTRUE_SMTP_USER", config.Kong.SMTP.User),
				fmt.Sprintf("%s=%s", "GOTRUE_SMTP_PASS", config.Kong.SMTP.Pass),
				fmt.Sprintf("%s=%s", "GOTRUE_SMTP_SENDER_NAME", config.Kong.SMTP.From.Name),
				fmt.Sprintf("%s=%s", "GOTRUE_MAILER_URLPATHS_INVITE", "/auth/v1/verify"),
				fmt.Sprintf("%s=%s", "GOTRUE_MAILER_URLPATHS_CONFIRMATION", "/auth/v1/verify"),
				fmt.Sprintf("%s=%s", "GOTRUE_MAILER_URLPATHS_RECOVERY", "/auth/v1/verify"),
				fmt.Sprintf("%s=%s", "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE", "/auth/v1/verify"),
				fmt.Sprintf("%s=%s", "GOTRUE_EXTERNAL_PHONE_ENABLED", "false"),
				fmt.Sprintf("%s=%s", "GOTRUE_SMS_AUTOCONFIRM", "false"),
			},
		}
	},

	ImgProxy: func(config Config) Service {
		if info, err := os.Stat(config.Storage.DataDirectory); err != nil {
			if os.IsNotExist(err) {
				if err := os.MkdirAll(config.Storage.DataDirectory, 0o700); err != nil {
					panic(fmt.Sprintf("storage data directory \"%s\" does not exist and an error occurred while trying to create it: %v", config.Storage.DataDirectory, err))
				}
			} else {
				panic(fmt.Sprintf("error checking storage data directory \"%s\" exists: %v", config.Storage.DataDirectory, err))
			}
		} else if !info.IsDir() {
			panic(fmt.Sprintf("storage directory \"%s\" exists but is not a directory", config.Storage.DataDirectory))
		}

		return Service{
			Name:    containerName(config, "supago-imgproxy"),
			Image:   "darthsim/imgproxy:v3.8.0",
			Aliases: []string{"imgproxy"},
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"imgproxy",
					"health",
				},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  3,
			},
			Mounts: []mount.Mount{
				{
					Type:   mount.TypeBind,
					Source: config.Storage.DataDirectory,
					Target: "/var/lib/storage",
				},
			},
			Env: []string{
				"IMGPROXY_BIND=:5001",
				"IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
				"IMGPROXY_USE_ETAG=true",
				"IMGPROXY_ENABLE_WEBP_DETECTION=true",
			},
		}
	},

	Kong: func(config Config) Service {
		return Service{
			Image:   "kong:2.8.1",
			Name:    containerName(config, kong.ContainerName),
			Aliases: []string{"kong"},
			Ports: []uint16{
				8000,
			},
			EmbeddedFiles: []EmbeddedFile{
				{
					Data: kong.ConfigFile,
					Path: "/usr/local/kong-template.yml",
				},
			},
			Entrypoint: []string{
				"bash", "-c",
				`set -euo pipefail
eval "echo \"$(cat /usr/local/kong-template.yml)\"" > "$HOME/kong.yml"
exec /docker-entrypoint.sh kong docker-start`,
			},
			Env: []string{
				fmt.Sprintf("%s=%s", "KONG_DATABASE", "off"),
				fmt.Sprintf("%s=%s", "KONG_DECLARATIVE_CONFIG", "/home/kong/kong.yml"),
				fmt.Sprintf("%s=%s", "KONG_DNS_ORDER", "LAST,A,CNAME"),
				fmt.Sprintf("%s=%s", "KONG_PLUGINS", "request-transformer,cors,key-auth,acl,basic-auth"),
				fmt.Sprintf("%s=%s", "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE", "160k"),
				fmt.Sprintf("%s=%s", "KONG_NGINX_PROXY_PROXY_BUFFERS", "64 160k"),
				fmt.Sprintf("%s=%s", "SUPABASE_ANON_KEY", config.Keys.PublicJwt),
				fmt.Sprintf("%s=%s", "SUPABASE_SERVICE_KEY", config.Keys.PrivateJwt),
				fmt.Sprintf("%s=%s", "DASHBOARD_USERNAME", config.Dashboard.Username),
				fmt.Sprintf("%s=%s", "DASHBOARD_PASSWORD", config.Dashboard.Password),
			},
		}
	},

	Meta: func(config Config) Service {
		return Service{
			Image:   "supabase/postgres-meta:v0.91.0",
			Name:    containerName(config, "supago-meta"),
			Aliases: []string{"meta"},
			Env: []string{
				fmt.Sprintf("%s=%s", "PG_META_PORT", "8080"),
				fmt.Sprintf("%s=%s", "PG_META_DB_HOST", containerName(config, dbContainerName)),
				fmt.Sprintf("%s=%s", "PG_META_DB_PORT", "5432"),
				fmt.Sprintf("%s=%s", "PG_META_DB_NAME", "postgres"),
				fmt.Sprintf("%s=%s", "PG_META_DB_USER", "supabase_admin"),
				fmt.Sprintf("%s=%s", "PG_META_DB_PASSWORD", config.Database.Password),
			},
		}
	},

	Postgres: func(config Config) Service {

		if info, err := os.Stat(config.Database.DataDirectory); err != nil {
			if os.IsNotExist(err) {
				if err := os.MkdirAll(config.Database.DataDirectory, 0o700); err != nil {
					panic(fmt.Sprintf("postgres data directory \"%s\" does not exist and an error occurred while trying to create it: %v", config.Database.DataDirectory, err))
				}
			} else {
				panic(fmt.Sprintf("error checking postgres data directory \"%s\" exists: %v", config.Database.DataDirectory, err))
			}
		} else if !info.IsDir() {
			panic(fmt.Sprintf("postgres data directory \"%s\" exists but is not a directory", config.Database.DataDirectory))
		}

		return Service{
			Image: "supabase/postgres:17.4.1.055",
			Name:  containerName(config, dbContainerName),
			Mounts: []mount.Mount{
				{
					Type:   mount.TypeBind,
					Source: config.Database.DataDirectory,
					Target: "/var/lib/postgresql/data",
				},
			},
			Ports:       make([]uint16, 0),
			StopTimeout: utils.Pointer(10 * time.Second),
			Aliases:     []string{"db"},
			Healthcheck: &container.HealthConfig{
				Test:     []string{"CMD", "pg_isready", "-U", "postgres", "-h", "localhost"},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  10,
			},
			Cmd: []string{
				"postgres",
				"-c", "config_file=/etc/postgresql/postgresql.conf",
				"-c", "log_min_messages=error",
				"-c", "archive_mode=off",
			},
			Env: []string{
				"POSTGRES_HOST=/var/run/postgresql",
				"PGPORT=5432",
				"POSTGRES_PORT=5432",
				fmt.Sprintf("PGPASSWORD=%s", config.Database.Password),
				fmt.Sprintf("POSTGRES_PASSWORD=%s", config.Database.Password),
				"PGDATABASE=postgres",
				"POSTGRES_DB=postgres",
				fmt.Sprintf("JWT_SECRET=%s", config.Keys.JwtSecret),
				"JWT_EXP=3600",
			},
			AfterStart: func(ctx context.Context, docker *client.Client, cid string) error {

				p := config.Database.Password
				output, err := utils.ExecInContainer(ctx, docker, cid, []string{
					"psql",
					"-h", "127.0.0.1",
					"-U", "supabase_admin",
					"-d", "postgres",
					"-v", "ON_ERROR_STOP=1",
					"-c",
					fmt.Sprintf(`
ALTER USER anon                    WITH PASSWORD '%s';
ALTER USER authenticated           WITH PASSWORD '%s';
ALTER USER authenticator           WITH PASSWORD '%s';
ALTER USER dashboard_user          WITH PASSWORD '%s';
ALTER USER pgbouncer               WITH PASSWORD '%s';
ALTER USER postgres                WITH PASSWORD '%s';
ALTER USER service_role            WITH PASSWORD '%s';
ALTER USER supabase_admin          WITH PASSWORD '%s';
ALTER USER supabase_auth_admin     WITH PASSWORD '%s';
ALTER USER supabase_read_only_user WITH PASSWORD '%s';
ALTER USER supabase_replication_admin WITH PASSWORD '%s';
ALTER USER supabase_storage_admin  WITH PASSWORD '%s';
`, p, p, p, p, p, p, p, p, p, p, p, p),
				})
				if err != nil {
					return fmt.Errorf("failed to patch postgres password: %v (%s)", err, strings.ReplaceAll(strings.TrimSpace(output), "\n", "\\n"))
				}
				return nil
			},
			EmbeddedFiles: []EmbeddedFile{
				{
					Path: "/etc/postgresql-custom/pgsodium_root.key",
					Data: []byte(config.Keys.PgSodiumEncryption),
				},
				{
					Data: postgres.RealtimeSQL,
					Path: "/docker-entrypoint-initdb.d/migrations/99-realtime.sql",
				},
				{
					Data: postgres.WebhooksSQL,
					Path: "/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql",
				},
				{
					Data: postgres.RolesSQL,
					Path: "/docker-entrypoint-initdb.d/init-scripts/99-roles.sql",
				},
				{
					Data: postgres.JwtSQL,
					Path: "/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql",
				},
				{
					Data: postgres.SupabaseSQL,
					Path: "/docker-entrypoint-initdb.d/migrations/97-_supabase.sql",
				},
				{
					Data: postgres.LogsSQL,
					Path: "/docker-entrypoint-initdb.d/migrations/99-logs.sql",
				},
				{
					Data: postgres.PoolerSQL,
					Path: "/docker-entrypoint-initdb.d/migrations/99-pooler.sql",
				},
			},
		}
	},

	Postgrest: func(config Config) Service {
		return Service{
			Image:   "postgrest/postgrest:v12.2.12",
			Name:    containerName(config, "supago-rest"),
			Aliases: []string{"rest"},
			Cmd:     []string{"postgrest"},
			Env: []string{
				fmt.Sprintf("PGRST_DB_URI=postgres://authenticator:%s@%s:5432/postgres", config.Database.Password, containerName(config, dbContainerName)),
				"PGRST_DB_SCHEMAS=public",
				"PGRST_DB_ANON_ROLE=anon",
				fmt.Sprintf("PGRST_JWT_SECRET=%s", config.Keys.JwtSecret),
				"PGRST_DB_USE_LEGACY_GUCS=false",
				fmt.Sprintf("PGRST_APP_SETTINGS_JWT_SECRET=%s", config.Keys.JwtSecret),
				"PGRST_APP_SETTINGS_JWT_EXP=3600",
				"PGRST_ADMIN_SERVER_PORT=3001",
			},
		}
	},

	Realtime: func(config Config) Service {
		return Service{
			Name:  "realtime-dev.supabase-realtime",
			Image: "supabase/realtime:v2.34.47",
			Aliases: []string{
				"supago-realtime",
				"realtime-dev.supabase-realtime",
				"realtime",
			},
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"curl",
					"-sSfL",
					"--head",
					"-o",
					"/dev/null",
					"-H",
					fmt.Sprintf("Authorization: Bearer %s", config.Keys.PublicJwt),
					"http://localhost:4000/api/tenants/realtime-dev/health",
				},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  3,
			},
			Env: []string{
				fmt.Sprintf("%s=%s", "PORT", "4000"),
				fmt.Sprintf("%s=%s", "DB_HOST", containerName(config, dbContainerName)),
				fmt.Sprintf("%s=%s", "DB_PORT", "5432"),
				fmt.Sprintf("%s=%s", "DB_USER", "supabase_admin"),
				fmt.Sprintf("%s=%s", "DB_PASSWORD", config.Database.Password),
				fmt.Sprintf("%s=%s", "DB_NAME", "postgres"),
				fmt.Sprintf("%s=%s", "DB_AFTER_CONNECT_QUERY", "SET search_path TO _realtime"),
				fmt.Sprintf("%s=%s", "DB_ENC_KEY", "supabaserealtime"),
				fmt.Sprintf("%s=%s", "API_JWT_SECRET", config.Keys.JwtSecret),
				fmt.Sprintf("%s=%s", "SECRET_KEY_BASE", utils.RandomString(64)),
				fmt.Sprintf("%s=%s", "ERL_AFLAGS", "-proto_dist inet_tcp"),
				fmt.Sprintf("%s=%s", "DNS_NODES", "''"),
				fmt.Sprintf("%s=%s", "RLIMIT_NOFILE", "10000"),
				fmt.Sprintf("%s=%s", "APP_NAME", "realtime"),
				fmt.Sprintf("%s=%s", "SEED_SELF_HOST", "true"),
				fmt.Sprintf("%s=%s", "RUN_JANITOR", "true"),
			},
		}
	},

	Storage: func(config Config) Service {
		if info, err := os.Stat(config.Storage.DataDirectory); err != nil {
			if os.IsNotExist(err) {
				if err := os.MkdirAll(config.Storage.DataDirectory, 0o700); err != nil {
					panic(fmt.Sprintf("storage data directory \"%s\" does not exist and an error occurred while trying to create it: %v", config.Storage.DataDirectory, err))
				}
			} else {
				panic(fmt.Sprintf("error checking storage data directory \"%s\" exists: %v", config.Storage.DataDirectory, err))
			}
		} else if !info.IsDir() {
			panic(fmt.Sprintf("storage directory \"%s\" exists but is not a directory", config.Storage.DataDirectory))
		}

		return Service{
			Name:    containerName(config, "supabase-storage"),
			Image:   "supabase/storage-api:v1.25.7",
			Aliases: []string{"storage"},
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"wget",
					"--no-verbose",
					"--tries=1",
					"--spider",
					"http://storage:5000/status",
				},
				Interval: 5 * time.Second,
				Timeout:  5 * time.Second,
				Retries:  3,
			},
			Mounts: []mount.Mount{
				{
					Type:   mount.TypeBind,
					Source: config.Storage.DataDirectory,
					Target: "/var/lib/storage",
				},
			},
			Env: []string{
				fmt.Sprintf("%s=%s", "ANON_KEY", config.Keys.PublicJwt),
				fmt.Sprintf("%s=%s", "SERVICE_KEY", config.Keys.PrivateJwt),
				fmt.Sprintf("%s=%s", "POSTGREST_URL", "http://rest:3000"),
				fmt.Sprintf("%s=%s", "PGRST_JWT_SECRET", config.Keys.JwtSecret),
				fmt.Sprintf("%s=%s", "DATABASE_URL",
					fmt.Sprintf("postgres://supabase_storage_admin:%s@%s:5432/postgres", config.Database.Password, containerName(config, dbContainerName))),
				fmt.Sprintf("%s=%s", "FILE_SIZE_LIMIT", "52428800"),
				fmt.Sprintf("%s=%s", "STORAGE_BACKEND", "file"),
				fmt.Sprintf("%s=%s", "FILE_STORAGE_BACKEND_PATH", "/var/lib/storage"),
				fmt.Sprintf("%s=%s", "TENANT_ID", "stub"),
				fmt.Sprintf("%s=%s", "REGION", "stub"),
				fmt.Sprintf("%s=%s", "GLOBAL_S3_BUCKET", "stub"),
				fmt.Sprintf("%s=%s", "ENABLE_IMAGE_TRANSFORMATION", "true"),
				fmt.Sprintf("%s=%s", "IMGPROXY_URL", "http://imgproxy:5001"),
			},
		}
	},

	Studio: func(config Config) Service {
		return Service{
			Image:   "supabase/studio:2025.06.30-sha-6f5982d",
			Name:    containerName(config, "supabase-studio"),
			Aliases: []string{"studio"},
			Healthcheck: &container.HealthConfig{
				Test: []string{
					"CMD",
					"node",
					"-e",
					"fetch('http://localhost:3000/api/platform/profile').then((r) => {if (r.status !== 200) throw new Error(r.status)})",
				},
				Interval: 5 * time.Second,
				Timeout:  10 * time.Second,
				Retries:  3,
			},
			Env: []string{

				"HOSTNAME=0.0.0.0",

				fmt.Sprintf("%s=%s", "STUDIO_PG_META_URL", "http://meta:8080"),
				fmt.Sprintf("%s=%s", "POSTGRES_PASSWORD", config.Database.Password),

				fmt.Sprintf("%s=%s", "DEFAULT_ORGANIZATION_NAME", "Supago"),
				fmt.Sprintf("%s=%s", "DEFAULT_PROJECT_NAME", "Supago"),

				fmt.Sprintf("%s=%s", "SUPABASE_URL", "http://kong:8000"),
				fmt.Sprintf("%s=%s", "SUPABASE_PUBLIC_URL", "http://127.0.0.1:8000"),
				fmt.Sprintf("%s=%s", "SUPABASE_ANON_KEY", config.Keys.PublicJwt),
				fmt.Sprintf("%s=%s", "SUPABASE_SERVICE_KEY", config.Keys.PrivateJwt),
				fmt.Sprintf("%s=%s", "AUTH_JWT_SECRET", config.Keys.JwtSecret),

				fmt.Sprintf("%s=%s", "LOGFLARE_PRIVATE_ACCESS_TOKEN", config.LogFlare.PrivateKey),
				fmt.Sprintf("%s=%s", "LOGFLARE_URL", "http://analytics:4000"),
				fmt.Sprintf("%s=%s", "NEXT_PUBLIC_ENABLE_LOGS", "true"),
				fmt.Sprintf("%s=%s", "NEXT_ANALYTICS_BACKEND_PROVIDER", "postgres"),
			},
		}
	},
}

func (PreBuiltServices) All ΒΆ added in v1.4.0

All get all services supported by SupaGo

type Service ΒΆ

type Service struct {
	Image      string
	Name       string
	Aliases    []string
	Entrypoint []string
	Cmd        []string
	Env        []string
	Labels     map[string]string
	// Mounts for local files/volumes mounted into the fs
	Mounts []mount.Mount
	// EmbeddedFiles for byte contents copied directly into the fs
	EmbeddedFiles []EmbeddedFile
	Ports         []uint16
	Healthcheck   *container.HealthConfig
	StopSignal    *string
	StopTimeout   *time.Duration
	AfterStart    func(ctx context.Context, docker *client.Client, containerID string) error
	// contains filtered or unexported fields
}

func (Service) Build ΒΆ added in v1.6.1

func (s Service) Build() ServiceConstructor

func (Service) String ΒΆ

func (s Service) String() string

type ServiceConstructor ΒΆ added in v1.4.0

type ServiceConstructor func(Config) Service

type StorageConfig ΒΆ

type StorageConfig struct {
	DataDirectory string
}

type SupaGo ΒΆ

type SupaGo struct {
	// contains filtered or unexported fields
}

func New ΒΆ

func New(config *Config) *SupaGo

func (*SupaGo) AddService ΒΆ

func (sg *SupaGo) AddService(constructor ServiceConstructor, constructors ...ServiceConstructor) *SupaGo

func (*SupaGo) AddServices ΒΆ

func (sg *SupaGo) AddServices(constructors func() []ServiceConstructor) *SupaGo

func (*SupaGo) Run ΒΆ

func (sg *SupaGo) Run(ctx context.Context) error

Run start and serve all services attached to the SupaGo instance

func (*SupaGo) RunForcefully ΒΆ

func (sg *SupaGo) RunForcefully(ctx context.Context) error

RunForcefully like Run, but will remove any conflicting containers destructively

func (*SupaGo) SetLogger ΒΆ

func (sg *SupaGo) SetLogger(logger *zap.SugaredLogger) *SupaGo

func (*SupaGo) Stop ΒΆ

func (sg *SupaGo) Stop()

Directories ΒΆ

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL