• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 19627513060

24 Nov 2025 08:11AM UTC coverage: 55.395% (+0.3%) from 55.094%
19627513060

Pull #4394

github

web-flow
Merge 000136c5e into 6d9e31644
Pull Request #4394: feat(cli): add concurrent images pulling

115 of 145 new or added lines in 2 files covered. (79.31%)

7 existing lines in 2 files now uncovered.

6659 of 12021 relevant lines covered (55.39%)

6.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.98
/internal/start/start.go
1
package start
2

3
import (
4
        "bytes"
5
        "context"
6
        _ "embed"
7
        "fmt"
8
        "io"
9
        "net"
10
        "net/url"
11
        "os"
12
        "path"
13
        "path/filepath"
14
        "slices"
15
        "strconv"
16
        "strings"
17
        "text/template"
18
        "time"
19

20
        "github.com/cenkalti/backoff/v4"
21
        "github.com/compose-spec/compose-go/v2/types"
22
        "github.com/docker/cli/cli/command"
23
        dockerFlags "github.com/docker/cli/cli/flags"
24
        "github.com/docker/compose/v2/pkg/api"
25
        "github.com/docker/compose/v2/pkg/compose"
26
        "github.com/docker/docker/api/types/container"
27
        "github.com/docker/docker/api/types/image"
28
        "github.com/docker/docker/api/types/network"
29
        "github.com/docker/docker/client"
30
        "github.com/docker/go-connections/nat"
31
        "github.com/go-errors/errors"
32
        "github.com/jackc/pgconn"
33
        "github.com/jackc/pgx/v4"
34
        "github.com/spf13/afero"
35

36
        "github.com/supabase/cli/internal/db/start"
37
        "github.com/supabase/cli/internal/functions/serve"
38
        "github.com/supabase/cli/internal/seed/buckets"
39
        "github.com/supabase/cli/internal/services"
40
        "github.com/supabase/cli/internal/status"
41
        "github.com/supabase/cli/internal/utils"
42
        "github.com/supabase/cli/internal/utils/flags"
43
        "github.com/supabase/cli/pkg/config"
44
)
45

46
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
3✔
47
        // Sanity checks.
3✔
48
        {
6✔
49
                if err := flags.LoadConfig(fsys); err != nil {
4✔
50
                        return err
1✔
51
                }
1✔
52
                if err := utils.AssertSupabaseDbIsRunning(); err == nil {
3✔
53
                        fmt.Fprintln(os.Stderr, utils.Aqua("supabase start")+" is already running.")
1✔
54
                        names := status.CustomName{}
1✔
55
                        return status.Run(ctx, names, utils.OutputPretty, fsys)
1✔
56
                } else if !errors.Is(err, utils.ErrNotRunning) {
3✔
57
                        return err
1✔
58
                }
1✔
59
                if err := flags.LoadProjectRef(fsys); err == nil {
×
60
                        _ = services.CheckVersions(ctx, fsys)
×
61
                }
×
62
        }
63

64
        dbConfig := pgconn.Config{
×
65
                Host:     utils.DbId,
×
66
                Port:     5432,
×
67
                User:     "postgres",
×
68
                Password: utils.Config.Db.Password,
×
69
                Database: "postgres",
×
70
        }
×
71
        if err := run(ctx, fsys, excludedContainers, dbConfig); err != nil {
×
72
                if ignoreHealthCheck && start.IsUnhealthyError(err) {
×
73
                        fmt.Fprintln(os.Stderr, err)
×
74
                } else {
×
75
                        if err := utils.DockerRemoveAll(context.Background(), os.Stderr, utils.Config.ProjectId); err != nil {
×
76
                                fmt.Fprintln(os.Stderr, err)
×
77
                        }
×
78
                        return err
×
79
                }
80
        }
81

82
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
83
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
84
        return nil
×
85
}
86

87
type kongConfig struct {
88
        GotrueId      string
89
        RestId        string
90
        RealtimeId    string
91
        StorageId     string
92
        StudioId      string
93
        PgmetaId      string
94
        EdgeRuntimeId string
95
        LogflareId    string
96
        PoolerId      string
97
        ApiHost       string
98
        ApiPort       uint16
99
        BearerToken   string
100
        QueryToken    string
101
}
102

103
var (
104
        //go:embed templates/kong.yml
105
        kongConfigEmbed    string
106
        kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
107

108
        //go:embed templates/custom_nginx.template
109
        nginxConfigEmbed string
110
        // Hardcoded configs which match nginxConfigEmbed
111
        nginxEmailTemplateDir   = "/home/kong/templates/email"
112
        nginxTemplateServerPort = 8088
113
)
114

115
type vectorConfig struct {
116
        ApiKey        string
117
        VectorId      string
118
        LogflareId    string
119
        KongId        string
120
        GotrueId      string
121
        RestId        string
122
        RealtimeId    string
123
        StorageId     string
124
        EdgeRuntimeId string
125
        DbId          string
126
}
127

128
var (
129
        //go:embed templates/vector.yaml
130
        vectorConfigEmbed    string
131
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
132
)
133

134
type poolerTenant struct {
135
        DbHost            string
136
        DbPort            uint16
137
        DbDatabase        string
138
        DbPassword        string
139
        ExternalId        string
140
        ModeType          config.PoolMode
141
        DefaultMaxClients uint
142
        DefaultPoolSize   uint
143
}
144

145
var (
146
        //go:embed templates/pooler.exs
147
        poolerTenantEmbed    string
148
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
149
)
150

151
var serviceTimeout = 30 * time.Second
152

153
// RetryClient wraps a Docker client to add retry logic for image pulls
154
type RetryClient struct {
155
        *client.Client
156
}
157

158
func isPermanentError(err error) bool {
14✔
159
        if err == nil {
28✔
160
                return false
14✔
161
        }
14✔
162
        // Rate limited errors can be recovered by retry
NEW
163
        if msg := err.Error(); strings.Contains(msg, "toomanyrequests:") {
×
NEW
164
                return false
×
NEW
165
        }
×
NEW
166
        return true
×
167
}
168

169
// ImagePull wraps the Docker client's ImagePull with retry logic and registry auth
NEW
170
func (cli *RetryClient) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) {
×
NEW
171
        if len(options.RegistryAuth) == 0 {
×
NEW
172
                options.RegistryAuth = utils.GetRegistryAuth()
×
NEW
173
        }
×
NEW
174
        pull := func() (io.ReadCloser, error) {
×
NEW
175
                resp, err := cli.Client.ImagePull(ctx, refStr, options)
×
NEW
176
                if isPermanentError(err) {
×
NEW
177
                        return resp, &backoff.PermanentError{Err: err}
×
NEW
178
                }
×
NEW
179
                return resp, err
×
180
        }
NEW
181
        policy := utils.NewBackoffPolicy(ctx)
×
NEW
182
        return backoff.RetryWithData(pull, policy)
×
183
}
184

185
// Also retry ImageInspect: https://github.com/docker/compose/blob/main/pkg/compose/pull.go#L174
186
func (cli *RetryClient) ImageInspect(ctx context.Context, refStr string, options ...client.ImageInspectOption) (image.InspectResponse, error) {
14✔
187
        pull := func() (image.InspectResponse, error) {
28✔
188
                resp, err := cli.Client.ImageInspect(ctx, refStr, options...)
14✔
189
                if isPermanentError(err) {
14✔
NEW
190
                        return resp, &backoff.PermanentError{Err: err}
×
NEW
191
                }
×
192
                return resp, err
14✔
193
        }
194
        policy := utils.NewBackoffPolicy(ctx)
14✔
195
        return backoff.RetryWithData(pull, policy)
14✔
196
}
197

198
// pullImagesUsingCompose pulls all required images using docker-compose service
199
func pullImagesUsingCompose(ctx context.Context, project types.Project) error {
2✔
200
        // Create Docker CLI
2✔
201
        cli, err := command.NewDockerCli()
2✔
202
        if err != nil {
2✔
NEW
203
                return errors.Errorf("failed to create Docker CLI: %w", err)
×
NEW
204
        }
×
205
        // Initialize Docker CLI
206
        opt := command.WithAPIClient(&RetryClient{Client: utils.Docker})
2✔
207
        if err := cli.Initialize(&dockerFlags.ClientOptions{}, opt); err != nil {
2✔
NEW
208
                return errors.Errorf("failed to initialize Docker CLI: %w", err)
×
NEW
209
        }
×
210
        service := compose.NewComposeService(cli)
2✔
211
        // Fallback to regular image pull by ignoring failures
2✔
212
        return service.Pull(ctx, &project, api.PullOptions{IgnoreFailures: true})
2✔
213
}
214

215
func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
216
        excluded := make(map[string]bool)
2✔
217
        for _, name := range excludedContainers {
17✔
218
                excluded[name] = true
15✔
219
        }
15✔
220
        notExcluded := func(sc types.ServiceConfig) bool {
28✔
221
                val, ok := excluded[sc.Name]
26✔
222
                return !val || !ok
26✔
223
        }
26✔
224

225
        jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
2✔
226
        if err != nil {
2✔
227
                return err
×
228
        }
×
229

230
        // TODO: start services using compose up
231
        project := types.Project{
2✔
232
                Name:     "supabase-cli",
2✔
233
                Services: utils.GetServices().Filter(notExcluded),
2✔
234
        }
2✔
235
        if err := pullImagesUsingCompose(ctx, project); err != nil {
2✔
NEW
236
                return err
×
NEW
237
        }
×
238

239
        // Start Postgres.
240
        if dbConfig.Host == utils.DbId {
4✔
241
                if err := start.StartDatabase(ctx, "", fsys, os.Stderr, options...); err != nil {
2✔
242
                        return err
×
243
                }
×
244
        }
245

246
        var started []string
2✔
247
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
248
        var isImgProxyEnabled = utils.Config.Storage.ImageTransformation != nil &&
2✔
249
                utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
2✔
250
        fmt.Fprintln(os.Stderr, "Starting containers...")
2✔
251

2✔
252
        // Start Logflare
2✔
253
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.Image, excluded) {
3✔
254
                env := []string{
1✔
255
                        "DB_DATABASE=_supabase",
1✔
256
                        "DB_HOSTNAME=" + dbConfig.Host,
1✔
257
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
258
                        "DB_SCHEMA=_analytics",
1✔
259
                        "DB_USERNAME=" + utils.SUPERUSER_ROLE,
1✔
260
                        "DB_PASSWORD=" + dbConfig.Password,
1✔
261
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
1✔
262
                        "LOGFLARE_SINGLE_TENANT=true",
1✔
263
                        "LOGFLARE_SUPABASE_MODE=true",
1✔
264
                        "LOGFLARE_PRIVATE_ACCESS_TOKEN=" + utils.Config.Analytics.ApiKey,
1✔
265
                        "LOGFLARE_LOG_LEVEL=warn",
1✔
266
                        "LOGFLARE_NODE_HOST=127.0.0.1",
1✔
267
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
1✔
268
                        "RELEASE_COOKIE=cookie",
1✔
269
                }
1✔
270
                bind := []string{}
1✔
271

1✔
272
                switch utils.Config.Analytics.Backend {
1✔
273
                case config.LogflareBigQuery:
×
274
                        workdir, err := os.Getwd()
×
275
                        if err != nil {
×
276
                                return errors.Errorf("failed to get working directory: %w", err)
×
277
                        }
×
278
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
279
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
280
                        // This is hardcoded in studio frontend
×
281
                        env = append(env,
×
282
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
283
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
284
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
285
                        )
×
286
                case config.LogflarePostgres:
1✔
287
                        env = append(env,
1✔
288
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
1✔
289
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
1✔
290
                        )
1✔
291
                }
292

293
                if _, err := utils.DockerStart(
1✔
294
                        ctx,
1✔
295
                        container.Config{
1✔
296
                                Hostname: "127.0.0.1",
1✔
297
                                Image:    utils.Config.Analytics.Image,
1✔
298
                                Env:      env,
1✔
299
                                // Original entrypoint conflicts with healthcheck due to 15 seconds sleep:
1✔
300
                                // https://github.com/Logflare/logflare/blob/staging/run.sh#L35
1✔
301
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > run.sh && sh run.sh
1✔
302
./logflare eval Logflare.Release.migrate
1✔
303
./logflare start --sname logflare
1✔
304
EOF
1✔
305
`},
1✔
306
                                Healthcheck: &container.HealthConfig{
1✔
307
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
308
                                                "http://127.0.0.1:4000/health",
1✔
309
                                        },
1✔
310
                                        Interval:    10 * time.Second,
1✔
311
                                        Timeout:     2 * time.Second,
1✔
312
                                        Retries:     3,
1✔
313
                                        StartPeriod: 10 * time.Second,
1✔
314
                                },
1✔
315
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
316
                        },
1✔
317
                        container.HostConfig{
1✔
318
                                Binds:         bind,
1✔
319
                                PortBindings:  nat.PortMap{"4000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.Port), 10)}}},
1✔
320
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
321
                        },
1✔
322
                        network.NetworkingConfig{
1✔
323
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
324
                                        utils.NetId: {
1✔
325
                                                Aliases: utils.LogflareAliases,
1✔
326
                                        },
1✔
327
                                },
1✔
328
                        },
1✔
329
                        utils.LogflareId,
1✔
330
                ); err != nil {
1✔
331
                        return err
×
332
                }
×
333
                started = append(started, utils.LogflareId)
1✔
334
        }
335

336
        // Start vector
337
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.VectorImage, excluded) {
3✔
338
                var vectorConfigBuf bytes.Buffer
1✔
339
                if err := vectorConfigTemplate.Option("missingkey=error").Execute(&vectorConfigBuf, vectorConfig{
1✔
340
                        ApiKey:        utils.Config.Analytics.ApiKey,
1✔
341
                        VectorId:      utils.VectorId,
1✔
342
                        LogflareId:    utils.LogflareId,
1✔
343
                        KongId:        utils.KongId,
1✔
344
                        GotrueId:      utils.GotrueId,
1✔
345
                        RestId:        utils.RestId,
1✔
346
                        RealtimeId:    utils.RealtimeId,
1✔
347
                        StorageId:     utils.StorageId,
1✔
348
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
349
                        DbId:          utils.DbId,
1✔
350
                }); err != nil {
1✔
351
                        return errors.Errorf("failed to exec template: %w", err)
×
352
                }
×
353
                var binds, env, securityOpts []string
1✔
354
                // Special case for GitLab pipeline
1✔
355
                parsed, err := client.ParseHostURL(utils.Docker.DaemonHost())
1✔
356
                if err != nil {
1✔
357
                        return errors.Errorf("failed to parse docker host: %w", err)
×
358
                }
×
359
                // Ref: https://vector.dev/docs/reference/configuration/sources/docker_logs/#docker_host
360
                dindHost := &url.URL{Scheme: "http", Host: net.JoinHostPort(utils.DinDHost, "2375")}
1✔
361
                switch parsed.Scheme {
1✔
362
                case "tcp":
×
363
                        if _, port, err := net.SplitHostPort(parsed.Host); err == nil {
×
364
                                dindHost.Host = net.JoinHostPort(utils.DinDHost, port)
×
365
                        }
×
366
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
367
                case "npipe":
×
368
                        const dockerDaemonNeededErr = "Analytics on Windows requires Docker daemon exposed on tcp://localhost:2375.\nSee https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=windows#running-supabase-locally for more details."
×
369
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), dockerDaemonNeededErr)
×
370
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
371
                case "unix":
×
372
                        if dindHost, err = client.ParseHostURL(client.DefaultDockerHost); err != nil {
×
373
                                return errors.Errorf("failed to parse default host: %w", err)
×
374
                        } else if strings.HasSuffix(parsed.Host, "/.docker/run/docker.sock") {
×
375
                                fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires mounting default docker socket:", dindHost.Host)
×
376
                                binds = append(binds, fmt.Sprintf("%[1]s:%[1]s:ro", dindHost.Host))
×
377
                        } else {
×
378
                                // Podman and OrbStack can mount root-less socket without issue
×
379
                                binds = append(binds, fmt.Sprintf("%s:%s:ro", parsed.Host, dindHost.Host))
×
380
                                securityOpts = append(securityOpts, "label:disable")
×
381
                        }
×
382
                }
383
                if _, err := utils.DockerStart(
1✔
384
                        ctx,
1✔
385
                        container.Config{
1✔
386
                                Image: utils.Config.Analytics.VectorImage,
1✔
387
                                Env:   env,
1✔
388
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/vector/vector.yaml && vector --config /etc/vector/vector.yaml
1✔
389
` + vectorConfigBuf.String() + `
1✔
390
EOF
1✔
391
`},
1✔
392
                                Healthcheck: &container.HealthConfig{
1✔
393
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
394
                                                "http://127.0.0.1:9001/health",
1✔
395
                                        },
1✔
396
                                        Interval: 10 * time.Second,
1✔
397
                                        Timeout:  2 * time.Second,
1✔
398
                                        Retries:  3,
1✔
399
                                },
1✔
400
                        },
1✔
401
                        container.HostConfig{
1✔
402
                                Binds:         binds,
1✔
403
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
404
                                SecurityOpt:   securityOpts,
1✔
405
                        },
1✔
406
                        network.NetworkingConfig{
1✔
407
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
408
                                        utils.NetId: {
1✔
409
                                                Aliases: utils.VectorAliases,
1✔
410
                                        },
1✔
411
                                },
1✔
412
                        },
1✔
413
                        utils.VectorId,
1✔
414
                ); err != nil {
1✔
415
                        return err
×
416
                }
×
417
                if parsed.Scheme != "npipe" {
2✔
418
                        started = append(started, utils.VectorId)
1✔
419
                }
1✔
420
        }
421

422
        // Start Kong.
423
        if !isContainerExcluded(utils.Config.Api.KongImage, excluded) {
3✔
424
                var kongConfigBuf bytes.Buffer
1✔
425
                if err := kongConfigTemplate.Option("missingkey=error").Execute(&kongConfigBuf, kongConfig{
1✔
426
                        GotrueId:      utils.GotrueId,
1✔
427
                        RestId:        utils.RestId,
1✔
428
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
429
                        StorageId:     utils.StorageId,
1✔
430
                        StudioId:      utils.StudioId,
1✔
431
                        PgmetaId:      utils.PgmetaId,
1✔
432
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
433
                        LogflareId:    utils.LogflareId,
1✔
434
                        PoolerId:      utils.PoolerId,
1✔
435
                        ApiHost:       utils.Config.Hostname,
1✔
436
                        ApiPort:       utils.Config.Api.Port,
1✔
437
                        BearerToken: fmt.Sprintf(
1✔
438
                                // If Authorization header is set to a self-minted JWT, we want to pass it down.
1✔
439
                                // Legacy supabase-js may set Authorization header to Bearer <apikey>. We must remove it
1✔
440
                                // to avoid failing JWT validation.
1✔
441
                                // If Authorization header is missing, we want to match against apikey header to set the
1✔
442
                                // default JWT for downstream services.
1✔
443
                                // Finally, the apikey header may be set to a legacy JWT. In that case, we want to copy
1✔
444
                                // it to Authorization header for backwards compatibility.
1✔
445
                                `$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '%s' and 'Bearer %s') or (headers.apikey == '%s' and 'Bearer %s') or headers.apikey)`,
1✔
446
                                utils.Config.Auth.SecretKey.Value,
1✔
447
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
448
                                utils.Config.Auth.PublishableKey.Value,
1✔
449
                                utils.Config.Auth.AnonKey.Value,
1✔
450
                        ),
1✔
451
                        QueryToken: fmt.Sprintf(
1✔
452
                                `$((query_params.apikey == '%s' and '%s') or (query_params.apikey == '%s' and '%s') or query_params.apikey)`,
1✔
453
                                utils.Config.Auth.SecretKey.Value,
1✔
454
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
455
                                utils.Config.Auth.PublishableKey.Value,
1✔
456
                                utils.Config.Auth.AnonKey.Value,
1✔
457
                        ),
1✔
458
                }); err != nil {
1✔
459
                        return errors.Errorf("failed to exec template: %w", err)
×
460
                }
×
461

462
                binds := []string{}
1✔
463
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
464
                        if len(tmpl.ContentPath) == 0 {
×
465
                                continue
×
466
                        }
467
                        hostPath := tmpl.ContentPath
×
468
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
469
                                var err error
×
470
                                hostPath, err = filepath.Abs(hostPath)
×
471
                                if err != nil {
×
472
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
473
                                }
×
474
                        }
475
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
476
                        binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath))
×
477
                }
478

479
                dockerPort := uint16(8000)
1✔
480
                if utils.Config.Api.Tls.Enabled {
1✔
481
                        dockerPort = 8443
×
482
                }
×
483
                if _, err := utils.DockerStart(
1✔
484
                        ctx,
1✔
485
                        container.Config{
1✔
486
                                Image: utils.Config.Api.KongImage,
1✔
487
                                Env: []string{
1✔
488
                                        "KONG_DATABASE=off",
1✔
489
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
490
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
491
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
492
                                        fmt.Sprintf("KONG_PORT_MAPS=%d:8000", utils.Config.Api.Port),
1✔
493
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
494
                                        // sizeable response headers azure can generate
1✔
495
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
496
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
497
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
498
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
499
                                        // Use modern TLS certificate
1✔
500
                                        "KONG_SSL_CERT=/home/kong/localhost.crt",
1✔
501
                                        "KONG_SSL_CERT_KEY=/home/kong/localhost.key",
1✔
502
                                },
1✔
503
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && \
1✔
504
cat <<'EOF' > /home/kong/custom_nginx.template && \
1✔
505
cat <<'EOF' > /home/kong/localhost.crt && \
1✔
506
cat <<'EOF' > /home/kong/localhost.key && \
1✔
507
./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template
1✔
508
` + kongConfigBuf.String() + `
1✔
509
EOF
1✔
510
` + nginxConfigEmbed + `
1✔
511
EOF
1✔
512
` + string(utils.Config.Api.Tls.CertContent) + `
1✔
513
EOF
1✔
514
` + string(utils.Config.Api.Tls.KeyContent) + `
1✔
515
EOF
1✔
516
`},
1✔
517
                                ExposedPorts: nat.PortSet{
1✔
518
                                        "8000/tcp": {},
1✔
519
                                        "8443/tcp": {},
1✔
520
                                        nat.Port(fmt.Sprintf("%d/tcp", nginxTemplateServerPort)): {},
1✔
521
                                },
1✔
522
                        },
1✔
523
                        container.HostConfig{
1✔
524
                                Binds: binds,
1✔
525
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
1✔
526
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)},
1✔
527
                                }},
1✔
528
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
529
                        },
1✔
530
                        network.NetworkingConfig{
1✔
531
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
532
                                        utils.NetId: {
1✔
533
                                                Aliases: utils.KongAliases,
1✔
534
                                        },
1✔
535
                                },
1✔
536
                        },
1✔
537
                        utils.KongId,
1✔
538
                ); err != nil {
1✔
539
                        return err
×
540
                }
×
541
                started = append(started, utils.KongId)
1✔
542
        }
543

544
        // Start GoTrue.
545
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
546
                var testOTP bytes.Buffer
1✔
547
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
548
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
549
                }
×
550

551
                env := []string{
1✔
552
                        "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl,
1✔
553

1✔
554
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
555
                        "GOTRUE_API_PORT=9999",
1✔
556

1✔
557
                        "GOTRUE_DB_DRIVER=postgres",
1✔
558
                        fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
559

1✔
560
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
561
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
562
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
563

1✔
564
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
565
                        "GOTRUE_JWT_AUD=authenticated",
1✔
566
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
567
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
568
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
569
                        "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer,
1✔
570

1✔
571
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
572
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
573
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
574
                        fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength),
1✔
575
                        fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry),
1✔
576

1✔
577
                        fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns),
1✔
578

1✔
579
                        fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency),
1✔
580

1✔
581
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
582
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
583
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
584
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
585
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
586

1✔
587
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
588
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
589
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
590
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
591
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
592
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
593
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
594

1✔
595
                        fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength),
1✔
596
                        fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()),
1✔
597
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
598
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
599
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
600
                        fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange),
1✔
601
                        fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
1✔
602
                        fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
1✔
603
                        fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
1✔
604
                        fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
1✔
605
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled),
1✔
606
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled),
1✔
607
                        fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
1✔
608

1✔
609
                        // Add rate limit configurations
1✔
610
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers),
1✔
611
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh),
1✔
612
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps),
1✔
613
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications),
1✔
614
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent),
1✔
615
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3),
1✔
616
                }
1✔
617

1✔
618
                // Since signing key is validated by ResolveJWKS, simply read the key file.
1✔
619
                if keys, err := afero.ReadFile(fsys, utils.Config.Auth.SigningKeysPath); err == nil && len(keys) > 0 {
1✔
620
                        env = append(env, "GOTRUE_JWT_KEYS="+string(keys))
×
621
                }
×
622

623
                if utils.Config.Auth.Email.Smtp != nil && utils.Config.Auth.Email.Smtp.Enabled {
1✔
624
                        env = append(env,
×
625
                                fmt.Sprintf("GOTRUE_RATE_LIMIT_EMAIL_SENT=%v", utils.Config.Auth.RateLimit.EmailSent),
×
626
                                fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host),
×
627
                                fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port),
×
628
                                fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User),
×
629
                                fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass.Value),
×
630
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail),
×
631
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName),
×
632
                        )
×
633
                } else if utils.Config.Inbucket.Enabled {
2✔
634
                        env = append(env,
1✔
635
                                "GOTRUE_SMTP_HOST="+utils.InbucketId,
1✔
636
                                "GOTRUE_SMTP_PORT=1025",
1✔
637
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Inbucket.AdminEmail),
1✔
638
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Inbucket.SenderName),
1✔
639
                        )
1✔
640
                }
1✔
641

642
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
643
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
644
                }
×
645
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
646
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
647
                }
×
648

649
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
650
                        if len(tmpl.ContentPath) > 0 {
×
651
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
652
                                        strings.ToUpper(id),
×
653
                                        utils.KongId,
×
654
                                        nginxTemplateServerPort,
×
655
                                        id+filepath.Ext(tmpl.ContentPath),
×
656
                                ))
×
657
                        }
×
658
                        if tmpl.Subject != nil {
×
659
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
660
                                        strings.ToUpper(id),
×
661
                                        *tmpl.Subject,
×
662
                                ))
×
663
                        }
×
664
                }
665

666
                switch {
1✔
667
                case utils.Config.Auth.Sms.Twilio.Enabled:
×
668
                        env = append(
×
669
                                env,
×
670
                                "GOTRUE_SMS_PROVIDER=twilio",
×
671
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
672
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken.Value,
×
673
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
674
                        )
×
675
                case utils.Config.Auth.Sms.TwilioVerify.Enabled:
×
676
                        env = append(
×
677
                                env,
×
678
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
679
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
680
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken.Value,
×
681
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
682
                        )
×
683
                case utils.Config.Auth.Sms.Messagebird.Enabled:
×
684
                        env = append(
×
685
                                env,
×
686
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
687
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey.Value,
×
688
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
689
                        )
×
690
                case utils.Config.Auth.Sms.Textlocal.Enabled:
×
691
                        env = append(
×
692
                                env,
×
693
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
694
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey.Value,
×
695
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
696
                        )
×
697
                case utils.Config.Auth.Sms.Vonage.Enabled:
×
698
                        env = append(
×
699
                                env,
×
700
                                "GOTRUE_SMS_PROVIDER=vonage",
×
701
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
702
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret.Value,
×
703
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
704
                        )
×
705
                }
706

707
                if captcha := utils.Config.Auth.Captcha; captcha != nil {
1✔
708
                        env = append(
×
709
                                env,
×
710
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_ENABLED=%v", captcha.Enabled),
×
711
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_PROVIDER=%v", captcha.Provider),
×
712
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_SECRET=%v", captcha.Secret.Value),
×
713
                        )
×
714
                }
×
715

716
                if hook := utils.Config.Auth.Hook.MFAVerificationAttempt; hook != nil && hook.Enabled {
1✔
717
                        env = append(
×
718
                                env,
×
719
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
720
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
721
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
722
                        )
×
723
                }
×
724
                if hook := utils.Config.Auth.Hook.PasswordVerificationAttempt; hook != nil && hook.Enabled {
1✔
725
                        env = append(
×
726
                                env,
×
727
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
728
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
729
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
730
                        )
×
731
                }
×
732
                if hook := utils.Config.Auth.Hook.CustomAccessToken; hook != nil && hook.Enabled {
1✔
733
                        env = append(
×
734
                                env,
×
735
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
736
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+hook.URI,
×
737
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets.Value,
×
738
                        )
×
739
                }
×
740
                if hook := utils.Config.Auth.Hook.SendSMS; hook != nil && hook.Enabled {
1✔
741
                        env = append(
×
742
                                env,
×
743
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
744
                                "GOTRUE_HOOK_SEND_SMS_URI="+hook.URI,
×
745
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets.Value,
×
746
                        )
×
747
                }
×
748
                if hook := utils.Config.Auth.Hook.SendEmail; hook != nil && hook.Enabled {
1✔
749
                        env = append(
×
750
                                env,
×
751
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
752
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+hook.URI,
×
753
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets.Value,
×
754
                        )
×
755
                }
×
756
                if hook := utils.Config.Auth.Hook.BeforeUserCreated; hook != nil && hook.Enabled {
1✔
757
                        env = append(
×
758
                                env,
×
759
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_ENABLED=true",
×
760
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_URI="+hook.URI,
×
761
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_SECRETS="+hook.Secrets.Value,
×
762
                        )
×
763
                }
×
764

765
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
766
                        env = append(
×
767
                                env,
×
768
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
769
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
770
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
771
                        )
×
772
                }
×
773

774
                for name, config := range utils.Config.Auth.External {
2✔
775
                        env = append(
1✔
776
                                env,
1✔
777
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
1✔
778
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
1✔
779
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value),
1✔
780
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
1✔
781
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_EMAIL_OPTIONAL=%t", strings.ToUpper(name), config.EmailOptional),
1✔
782
                        )
1✔
783

1✔
784
                        redirectUri := config.RedirectUri
1✔
785
                        if redirectUri == "" {
2✔
786
                                redirectUri = utils.Config.Auth.JwtIssuer + "/callback"
1✔
787
                        }
1✔
788
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
1✔
789

1✔
790
                        if config.Url != "" {
1✔
791
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
792
                        }
×
793
                }
794
                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=%v", utils.Config.Auth.Web3.Solana.Enabled))
1✔
795

1✔
796
                // OAuth server configuration
1✔
797
                if utils.Config.Auth.OAuthServer.Enabled {
1✔
798
                        env = append(env,
×
799
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ENABLED=%v", utils.Config.Auth.OAuthServer.Enabled),
×
800
                                "GOTRUE_OAUTH_SERVER_AUTHORIZATION_PATH="+utils.Config.Auth.OAuthServer.AuthorizationUrlPath,
×
801
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION=%v", utils.Config.Auth.OAuthServer.AllowDynamicRegistration),
×
802
                        )
×
803
                }
×
804

805
                if _, err := utils.DockerStart(
1✔
806
                        ctx,
1✔
807
                        container.Config{
1✔
808
                                Image:        utils.Config.Auth.Image,
1✔
809
                                Env:          env,
1✔
810
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
811
                                Healthcheck: &container.HealthConfig{
1✔
812
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
813
                                                "http://127.0.0.1:9999/health",
1✔
814
                                        },
1✔
815
                                        Interval: 10 * time.Second,
1✔
816
                                        Timeout:  2 * time.Second,
1✔
817
                                        Retries:  3,
1✔
818
                                },
1✔
819
                        },
1✔
820
                        container.HostConfig{
1✔
821
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
822
                        },
1✔
823
                        network.NetworkingConfig{
1✔
824
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
825
                                        utils.NetId: {
1✔
826
                                                Aliases: utils.GotrueAliases,
1✔
827
                                        },
1✔
828
                                },
1✔
829
                        },
1✔
830
                        utils.GotrueId,
1✔
831
                ); err != nil {
1✔
832
                        return err
×
833
                }
×
834
                started = append(started, utils.GotrueId)
1✔
835
        }
836

837
        // Start Mailpit
838
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
839
                inbucketPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
840
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
841
                        inbucketPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
842
                }
×
843
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
844
                        inbucketPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
845
                }
×
846
                if _, err := utils.DockerStart(
1✔
847
                        ctx,
1✔
848
                        container.Config{
1✔
849
                                Image: utils.Config.Inbucket.Image,
1✔
850
                                Env: []string{
1✔
851
                                        // Disable reverse DNS lookups in Mailpit to avoid slow/delayed DNS resolution
1✔
852
                                        "MP_SMTP_DISABLE_RDNS=true",
1✔
853
                                },
1✔
854
                                Healthcheck: &container.HealthConfig{
1✔
855
                                        Test:     []string{"CMD", "/mailpit", "readyz"},
1✔
856
                                        Interval: 10 * time.Second,
1✔
857
                                        Timeout:  2 * time.Second,
1✔
858
                                        Retries:  3,
1✔
859
                                        // StartPeriod taken from upstream Dockerfile
1✔
860
                                        StartPeriod: 10 * time.Second,
1✔
861
                                },
1✔
862
                        },
1✔
863
                        container.HostConfig{
1✔
864
                                PortBindings:  inbucketPortBindings,
1✔
865
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
866
                        },
1✔
867
                        network.NetworkingConfig{
1✔
868
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
869
                                        utils.NetId: {
1✔
870
                                                Aliases: utils.InbucketAliases,
1✔
871
                                        },
1✔
872
                                },
1✔
873
                        },
1✔
874
                        utils.InbucketId,
1✔
875
                ); err != nil {
1✔
876
                        return err
×
877
                }
×
878
                started = append(started, utils.InbucketId)
1✔
879
        }
880

881
        // Start Realtime.
882
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.Config.Realtime.Image, excluded) {
3✔
883
                if _, err := utils.DockerStart(
1✔
884
                        ctx,
1✔
885
                        container.Config{
1✔
886
                                Image: utils.Config.Realtime.Image,
1✔
887
                                Env: []string{
1✔
888
                                        "PORT=4000",
1✔
889
                                        "DB_HOST=" + dbConfig.Host,
1✔
890
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
891
                                        "DB_USER=" + utils.SUPERUSER_ROLE,
1✔
892
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
893
                                        "DB_NAME=" + dbConfig.Database,
1✔
894
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
895
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
896
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
897
                                        fmt.Sprintf("API_JWT_JWKS=%s", jwks),
1✔
898
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
899
                                        "APP_NAME=realtime",
1✔
900
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
901
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
902
                                        "DNS_NODES=''",
1✔
903
                                        "RLIMIT_NOFILE=",
1✔
904
                                        "SEED_SELF_HOST=true",
1✔
905
                                        "RUN_JANITOR=true",
1✔
906
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
907
                                },
1✔
908
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
909
                                Healthcheck: &container.HealthConfig{
1✔
910
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
911
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
912
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
913
                                                "http://127.0.0.1:4000/api/ping",
1✔
914
                                        },
1✔
915
                                        Interval: 10 * time.Second,
1✔
916
                                        Timeout:  2 * time.Second,
1✔
917
                                        Retries:  3,
1✔
918
                                },
1✔
919
                        },
1✔
920
                        container.HostConfig{
1✔
921
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
922
                        },
1✔
923
                        network.NetworkingConfig{
1✔
924
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
925
                                        utils.NetId: {
1✔
926
                                                Aliases: utils.RealtimeAliases,
1✔
927
                                        },
1✔
928
                                },
1✔
929
                        },
1✔
930
                        utils.RealtimeId,
1✔
931
                ); err != nil {
1✔
932
                        return err
×
933
                }
×
934
                started = append(started, utils.RealtimeId)
1✔
935
        }
936

937
        // Start PostgREST.
938
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
939
                if _, err := utils.DockerStart(
1✔
940
                        ctx,
1✔
941
                        container.Config{
1✔
942
                                Image: utils.Config.Api.Image,
1✔
943
                                Env: []string{
1✔
944
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
945
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
946
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
947
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
948
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
949
                                        fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
1✔
950
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
951
                                },
1✔
952
                                // PostgREST does not expose a shell for health check
1✔
953
                        },
1✔
954
                        container.HostConfig{
1✔
955
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
956
                        },
1✔
957
                        network.NetworkingConfig{
1✔
958
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
959
                                        utils.NetId: {
1✔
960
                                                Aliases: utils.RestAliases,
1✔
961
                                        },
1✔
962
                                },
1✔
963
                        },
1✔
964
                        utils.RestId,
1✔
965
                ); err != nil {
1✔
966
                        return err
×
967
                }
×
968
                started = append(started, utils.RestId)
1✔
969
        }
970

971
        // Start Storage.
972
        if isStorageEnabled {
3✔
973
                dockerStoragePath := "/mnt"
1✔
974
                if _, err := utils.DockerStart(
1✔
975
                        ctx,
1✔
976
                        container.Config{
1✔
977
                                Image: utils.Config.Storage.Image,
1✔
978
                                Env: []string{
1✔
979
                                        "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
1✔
980
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
981
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
982
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
983
                                        fmt.Sprintf("JWT_JWKS=%s", jwks),
1✔
984
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
985
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
986
                                        "STORAGE_BACKEND=file",
1✔
987
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
988
                                        "TENANT_ID=stub",
1✔
989
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
990
                                        "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
1✔
991
                                        "GLOBAL_S3_BUCKET=stub",
1✔
992
                                        fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
1✔
993
                                        fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
1✔
994
                                        "TUS_URL_PATH=/storage/v1/upload/resumable",
1✔
995
                                        "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
1✔
996
                                        "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
1✔
997
                                        "S3_PROTOCOL_PREFIX=/storage/v1",
1✔
998
                                        "UPLOAD_FILE_SIZE_LIMIT=52428800000",
1✔
999
                                        "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
1✔
1000
                                        "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200",
1✔
1001
                                },
1✔
1002
                                Healthcheck: &container.HealthConfig{
1✔
1003
                                        // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
1004
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
1005
                                                "http://127.0.0.1:5000/status",
1✔
1006
                                        },
1✔
1007
                                        Interval: 10 * time.Second,
1✔
1008
                                        Timeout:  2 * time.Second,
1✔
1009
                                        Retries:  3,
1✔
1010
                                },
1✔
1011
                        },
1✔
1012
                        container.HostConfig{
1✔
1013
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1014
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
1015
                        },
1✔
1016
                        network.NetworkingConfig{
1✔
1017
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1018
                                        utils.NetId: {
1✔
1019
                                                Aliases: utils.StorageAliases,
1✔
1020
                                        },
1✔
1021
                                },
1✔
1022
                        },
1✔
1023
                        utils.StorageId,
1✔
1024
                ); err != nil {
1✔
1025
                        return err
×
1026
                }
×
1027
                started = append(started, utils.StorageId)
1✔
1028
        }
1029

1030
        // Start Storage ImgProxy.
1031
        if isStorageEnabled && isImgProxyEnabled {
2✔
1032
                if _, err := utils.DockerStart(
×
1033
                        ctx,
×
1034
                        container.Config{
×
1035
                                Image: utils.Config.Storage.ImgProxyImage,
×
1036
                                Env: []string{
×
1037
                                        "IMGPROXY_BIND=:5001",
×
1038
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
×
1039
                                        "IMGPROXY_USE_ETAG=/",
×
1040
                                        "IMGPROXY_MAX_SRC_RESOLUTION=50",
×
1041
                                        "IMGPROXY_MAX_SRC_FILE_SIZE=25000000",
×
1042
                                        "IMGPROXY_MAX_ANIMATION_FRAMES=60",
×
1043
                                        "IMGPROXY_ENABLE_WEBP_DETECTION=true",
×
1044
                                        "IMGPROXY_PRESETS=default=width:3000/height:8192",
×
1045
                                        "IMGPROXY_FORMAT_QUALITY=jpeg=80,avif=62,webp=80",
×
1046
                                },
×
1047
                                Healthcheck: &container.HealthConfig{
×
1048
                                        Test:     []string{"CMD", "imgproxy", "health"},
×
1049
                                        Interval: 10 * time.Second,
×
1050
                                        Timeout:  2 * time.Second,
×
1051
                                        Retries:  3,
×
1052
                                },
×
1053
                        },
×
1054
                        container.HostConfig{
×
1055
                                VolumesFrom:   []string{utils.StorageId},
×
1056
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
1057
                        },
×
1058
                        network.NetworkingConfig{
×
1059
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
1060
                                        utils.NetId: {
×
1061
                                                Aliases: utils.ImgProxyAliases,
×
1062
                                        },
×
1063
                                },
×
1064
                        },
×
1065
                        utils.ImgProxyId,
×
1066
                ); err != nil {
×
1067
                        return err
×
1068
                }
×
1069
                started = append(started, utils.ImgProxyId)
×
1070
        }
1071

1072
        // Start all functions.
1073
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.Config.EdgeRuntime.Image, excluded) {
3✔
1074
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
1075
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, fsys); err != nil {
1✔
1076
                        return err
×
1077
                }
×
1078
                started = append(started, utils.EdgeRuntimeId)
1✔
1079
        }
1080

1081
        // Start pg-meta.
1082
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
1083
                if _, err := utils.DockerStart(
1✔
1084
                        ctx,
1✔
1085
                        container.Config{
1✔
1086
                                Image: utils.Config.Studio.PgmetaImage,
1✔
1087
                                Env: []string{
1✔
1088
                                        "PG_META_PORT=8080",
1✔
1089
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
1090
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
1091
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
1092
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
1093
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
1094
                                },
1✔
1095
                                Healthcheck: &container.HealthConfig{
1✔
1096
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:8080/health').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
1097
                                        Interval: 10 * time.Second,
1✔
1098
                                        Timeout:  2 * time.Second,
1✔
1099
                                        Retries:  3,
1✔
1100
                                },
1✔
1101
                        },
1✔
1102
                        container.HostConfig{
1✔
1103
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1104
                        },
1✔
1105
                        network.NetworkingConfig{
1✔
1106
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1107
                                        utils.NetId: {
1✔
1108
                                                Aliases: utils.PgmetaAliases,
1✔
1109
                                        },
1✔
1110
                                },
1✔
1111
                        },
1✔
1112
                        utils.PgmetaId,
1✔
1113
                ); err != nil {
1✔
1114
                        return err
×
1115
                }
×
1116
                started = append(started, utils.PgmetaId)
1✔
1117
        }
1118

1119
        // Start Studio.
1120
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.Image, excluded) {
3✔
1121
                if _, err := utils.DockerStart(
1✔
1122
                        ctx,
1✔
1123
                        container.Config{
1✔
1124
                                Image: utils.Config.Studio.Image,
1✔
1125
                                Env: []string{
1✔
1126
                                        "CURRENT_CLI_VERSION=" + utils.Version,
1✔
1127
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
1128
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
1129
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
1130
                                        "SUPABASE_PUBLIC_URL=" + utils.Config.Studio.ApiUrl,
1✔
1131
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
1132
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
1133
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
1134
                                        "LOGFLARE_PRIVATE_ACCESS_TOKEN=" + utils.Config.Analytics.ApiKey,
1✔
1135
                                        "OPENAI_API_KEY=" + utils.Config.Studio.OpenaiApiKey.Value,
1✔
1136
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
1137
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
1138
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
1139
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
1140
                                        "HOSTNAME=0.0.0.0",
1✔
1141
                                },
1✔
1142
                                Healthcheck: &container.HealthConfig{
1✔
1143
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:3000/api/platform/profile').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
1144
                                        Interval: 10 * time.Second,
1✔
1145
                                        Timeout:  2 * time.Second,
1✔
1146
                                        Retries:  3,
1✔
1147
                                },
1✔
1148
                        },
1✔
1149
                        container.HostConfig{
1✔
1150
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
1151
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1152
                        },
1✔
1153
                        network.NetworkingConfig{
1✔
1154
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1155
                                        utils.NetId: {
1✔
1156
                                                Aliases: utils.StudioAliases,
1✔
1157
                                        },
1✔
1158
                                },
1✔
1159
                        },
1✔
1160
                        utils.StudioId,
1✔
1161
                ); err != nil {
1✔
1162
                        return err
×
1163
                }
×
1164
                started = append(started, utils.StudioId)
1✔
1165
        }
1166

1167
        // Start pooler.
1168
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
1169
                portSession := uint16(5432)
×
1170
                portTransaction := uint16(6543)
×
1171
                dockerPort := portTransaction
×
1172
                if utils.Config.Db.Pooler.PoolMode == config.SessionMode {
×
1173
                        dockerPort = portSession
×
1174
                }
×
1175
                // Create pooler tenant
1176
                var poolerTenantBuf bytes.Buffer
×
1177
                if err := poolerTenantTemplate.Option("missingkey=error").Execute(&poolerTenantBuf, poolerTenant{
×
1178
                        DbHost:            dbConfig.Host,
×
1179
                        DbPort:            dbConfig.Port,
×
1180
                        DbDatabase:        dbConfig.Database,
×
1181
                        DbPassword:        dbConfig.Password,
×
1182
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
1183
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
1184
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
1185
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
1186
                }); err != nil {
×
1187
                        return errors.Errorf("failed to exec template: %w", err)
×
1188
                }
×
1189
                if _, err := utils.DockerStart(
×
1190
                        ctx,
×
1191
                        container.Config{
×
1192
                                Image: utils.Config.Db.Pooler.Image,
×
1193
                                Env: []string{
×
1194
                                        "PORT=4000",
×
1195
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
1196
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
1197
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
×
1198
                                        "CLUSTER_POSTGRES=true",
×
1199
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
1200
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
1201
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1202
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1203
                                        "REGION=local",
×
1204
                                        "RUN_JANITOR=true",
×
1205
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
×
1206
                                        "RLIMIT_NOFILE=",
×
1207
                                },
×
1208
                                Cmd: []string{
×
1209
                                        "/bin/sh", "-c",
×
1210
                                        fmt.Sprintf("/app/bin/migrate && /app/bin/supavisor eval '%s' && /app/bin/server", poolerTenantBuf.String()),
×
1211
                                },
×
1212
                                ExposedPorts: nat.PortSet{
×
1213
                                        "4000/tcp": {},
×
1214
                                        nat.Port(fmt.Sprintf("%d/tcp", portSession)):     {},
×
1215
                                        nat.Port(fmt.Sprintf("%d/tcp", portTransaction)): {},
×
1216
                                },
×
1217
                                Healthcheck: &container.HealthConfig{
×
1218
                                        Test:     []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/api/health"},
×
1219
                                        Interval: 10 * time.Second,
×
1220
                                        Timeout:  2 * time.Second,
×
1221
                                        Retries:  3,
×
1222
                                },
×
1223
                        },
×
1224
                        container.HostConfig{
×
1225
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
×
1226
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)},
×
1227
                                }},
×
1228
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
1229
                        },
×
1230
                        network.NetworkingConfig{
×
1231
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
1232
                                        utils.NetId: {
×
1233
                                                Aliases: utils.PoolerAliases,
×
1234
                                        },
×
1235
                                },
×
1236
                        },
×
1237
                        utils.PoolerId,
×
1238
                ); err != nil {
×
1239
                        return err
×
1240
                }
×
1241
                started = append(started, utils.PoolerId)
×
1242
        }
1243

1244
        fmt.Fprintln(os.Stderr, "Waiting for health checks...")
2✔
1245
        if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
3✔
1246
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1247
                        return err
×
1248
                }
×
1249
                // Disable prompts when seeding
1250
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
1251
                        return err
×
1252
                }
×
1253
        }
1254
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1255
}
1256

1257
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
22✔
1258
        short := utils.ShortContainerImageName(imageName)
22✔
1259
        val, ok := excluded[short]
22✔
1260
        return ok && val
22✔
1261
}
22✔
1262

1263
func ExcludableContainers() []string {
1✔
1264
        names := []string{}
1✔
1265
        for _, image := range config.Images.Services() {
14✔
1266
                names = append(names, utils.ShortContainerImageName(image))
13✔
1267
        }
13✔
1268
        return names
1✔
1269
}
1270

1271
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1272
        numOfKeyPairs := len(input)
5✔
1273
        i := 0
5✔
1274
        for k, v := range input {
15✔
1275
                output.WriteString(k)
10✔
1276
                output.WriteString(":")
10✔
1277
                output.WriteString(v)
10✔
1278
                i++
10✔
1279
                if i < numOfKeyPairs {
16✔
1280
                        output.WriteString(",")
6✔
1281
                }
6✔
1282
        }
1283
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc