Skip to content

Commit b2fcaa5

Browse files
jsjclaude
andcommitted
docs: add reviewer-facing comments for apple-container runtime
Add explanatory comments addressing likely review questions: - Why strategy-via-conditionals instead of an interface - Kong startup order change (IP resolution needs running containers) - Analytics log forwarding architecture (no Docker log driver) - PGDATA override for Apple container volume mounts - KongId added to listServicesToRestart (behavioral change) - Host header in kong.yml for Realtime tenant routing - Stale container reconciliation rationale - Apple volume delete lacks force flag Also add saveResetTestState helper to reduce verbose save/restore boilerplate in reset_test.go. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a3c5259 commit b2fcaa5

File tree

7 files changed

+126
-44
lines changed

7 files changed

+126
-44
lines changed

internal/db/reset/reset.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ func Run(ctx context.Context, version string, last uint, config pgconn.Config, f
110110
return nil
111111
}
112112

113+
// shouldRefreshAPIAfterReset returns true when Kong must be recreated after a
114+
// database reset. Apple containers assign dynamic IPs, so Kong's cached
115+
// upstream addresses become stale when the database container is replaced.
113116
func shouldRefreshAPIAfterReset() bool {
114117
return utils.UsesAppleContainerRuntime() && utils.Config.Api.Enabled
115118
}
@@ -303,6 +306,10 @@ func restartServices(ctx context.Context) error {
303306
return errors.Join(result...)
304307
}
305308

309+
// listServicesToRestart returns containers that need restarting after a
310+
// database reset. Kong is included because it caches upstream addresses that
311+
// may change when the database container is recreated (especially on Apple
312+
// containers which use dynamic IPs).
306313
func listServicesToRestart() []string {
307314
return []string{utils.StorageId, utils.GotrueId, utils.RealtimeId, utils.PoolerId, utils.KongId}
308315
}

internal/db/reset/reset_test.go

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,83 @@ import (
2424
"github.com/supabase/cli/internal/testing/apitest"
2525
"github.com/supabase/cli/internal/testing/fstest"
2626
"github.com/supabase/cli/internal/utils"
27+
"github.com/supabase/cli/pkg/config"
2728
"github.com/supabase/cli/pkg/pgtest"
2829
"github.com/supabase/cli/pkg/storage"
2930
)
3031

32+
// saveResetTestState captures and restores all package-level vars that the
33+
// apple-container reset tests override, reducing repetitive save/restore
34+
// boilerplate in individual test cases.
35+
func saveResetTestState(t *testing.T) {
36+
t.Helper()
37+
orig := struct {
38+
runtime string
39+
apiEnabled bool
40+
assertRunning func() error
41+
removeContainer func(context.Context, string, bool, bool) error
42+
removeVolume func(context.Context, string, bool) error
43+
startContainer func(context.Context, container.Config, container.HostConfig, network.NetworkingConfig, string) (string, error)
44+
inspectContainer func(context.Context, string) (utils.ContainerInfo, error)
45+
restartContainer func(context.Context, string) error
46+
waitForHealthy func(context.Context, time.Duration, ...string) error
47+
waitForLocalDB func(context.Context, time.Duration, ...func(*pgx.ConnConfig)) error
48+
waitForLocalAPI func(context.Context, time.Duration) error
49+
setupLocalDB func(context.Context, string, afero.Fs, io.Writer, ...func(*pgx.ConnConfig)) error
50+
restartKongFn func(context.Context, start.KongDependencies) error
51+
runBucketSeedFn func(context.Context, string, bool, afero.Fs) error
52+
seedBucketsFn func(context.Context, afero.Fs) error
53+
dbId, storageId string
54+
gotrueId, realtimeId string
55+
poolerId, kongId string
56+
}{
57+
runtime: string(utils.Config.Local.Runtime),
58+
apiEnabled: utils.Config.Api.Enabled,
59+
assertRunning: assertSupabaseDbIsRunning,
60+
removeContainer: removeContainer,
61+
removeVolume: removeVolume,
62+
startContainer: startContainer,
63+
inspectContainer: inspectContainer,
64+
restartContainer: restartContainer,
65+
waitForHealthy: waitForHealthyService,
66+
waitForLocalDB: waitForLocalDatabase,
67+
waitForLocalAPI: waitForLocalAPI,
68+
setupLocalDB: setupLocalDatabase,
69+
restartKongFn: restartKong,
70+
runBucketSeedFn: runBucketSeed,
71+
seedBucketsFn: seedBuckets,
72+
dbId: utils.DbId,
73+
storageId: utils.StorageId,
74+
gotrueId: utils.GotrueId,
75+
realtimeId: utils.RealtimeId,
76+
poolerId: utils.PoolerId,
77+
kongId: utils.KongId,
78+
}
79+
t.Cleanup(func() {
80+
utils.Config.Local.Runtime = config.LocalRuntime(orig.runtime)
81+
utils.Config.Api.Enabled = orig.apiEnabled
82+
assertSupabaseDbIsRunning = orig.assertRunning
83+
removeContainer = orig.removeContainer
84+
removeVolume = orig.removeVolume
85+
startContainer = orig.startContainer
86+
inspectContainer = orig.inspectContainer
87+
restartContainer = orig.restartContainer
88+
waitForHealthyService = orig.waitForHealthy
89+
waitForLocalDatabase = orig.waitForLocalDB
90+
waitForLocalAPI = orig.waitForLocalAPI
91+
setupLocalDatabase = orig.setupLocalDB
92+
restartKong = orig.restartKongFn
93+
runBucketSeed = orig.runBucketSeedFn
94+
seedBuckets = orig.seedBucketsFn
95+
utils.DbId = orig.dbId
96+
utils.StorageId = orig.storageId
97+
utils.GotrueId = orig.gotrueId
98+
utils.RealtimeId = orig.realtimeId
99+
utils.PoolerId = orig.poolerId
100+
utils.KongId = orig.kongId
101+
})
102+
}
103+
31104
func TestResetCommand(t *testing.T) {
32105
utils.Config.Hostname = "127.0.0.1"
33106
utils.Config.Db.Port = 5432
@@ -170,49 +243,7 @@ func TestResetCommand(t *testing.T) {
170243
})
171244

172245
t.Run("uses runtime helpers on apple container runtime", func(t *testing.T) {
173-
originalRuntime := utils.Config.Local.Runtime
174-
originalAPIEnabled := utils.Config.Api.Enabled
175-
originalAssertRunning := assertSupabaseDbIsRunning
176-
originalRemoveContainer := removeContainer
177-
originalRemoveVolume := removeVolume
178-
originalStartContainer := startContainer
179-
originalInspectContainer := inspectContainer
180-
originalRestartContainer := restartContainer
181-
originalWaitForHealthyService := waitForHealthyService
182-
originalWaitForLocalDatabase := waitForLocalDatabase
183-
originalWaitForLocalAPI := waitForLocalAPI
184-
originalSetupLocalDatabase := setupLocalDatabase
185-
originalRestartKong := restartKong
186-
originalRunBucketSeed := runBucketSeed
187-
originalDbID := utils.DbId
188-
originalStorageID := utils.StorageId
189-
originalGotrueID := utils.GotrueId
190-
originalRealtimeID := utils.RealtimeId
191-
originalPoolerID := utils.PoolerId
192-
originalKongID := utils.KongId
193-
194-
t.Cleanup(func() {
195-
utils.Config.Local.Runtime = originalRuntime
196-
utils.Config.Api.Enabled = originalAPIEnabled
197-
assertSupabaseDbIsRunning = originalAssertRunning
198-
removeContainer = originalRemoveContainer
199-
removeVolume = originalRemoveVolume
200-
startContainer = originalStartContainer
201-
inspectContainer = originalInspectContainer
202-
restartContainer = originalRestartContainer
203-
waitForHealthyService = originalWaitForHealthyService
204-
waitForLocalDatabase = originalWaitForLocalDatabase
205-
waitForLocalAPI = originalWaitForLocalAPI
206-
setupLocalDatabase = originalSetupLocalDatabase
207-
restartKong = originalRestartKong
208-
runBucketSeed = originalRunBucketSeed
209-
utils.DbId = originalDbID
210-
utils.StorageId = originalStorageID
211-
utils.GotrueId = originalGotrueID
212-
utils.RealtimeId = originalRealtimeID
213-
utils.PoolerId = originalPoolerID
214-
utils.KongId = originalKongID
215-
})
246+
saveResetTestState(t)
216247

217248
utils.Config.Local.Runtime = "apple-container"
218249
utils.Config.Db.MajorVersion = 15

internal/db/start/start.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ var (
4040
resolveContainerIP = utils.GetContainerIP
4141
)
4242

43+
// runtimePostgresConfig returns the postgresql.conf snippet, adding a custom
44+
// data_directory on Apple containers to match the PGDATA env var override
45+
// (see NewContainerConfig).
4346
func runtimePostgresConfig() string {
4447
settings := utils.Config.Db.Settings.ToPostgresConfig()
4548
if utils.UsesAppleContainerRuntime() {
@@ -86,6 +89,9 @@ func NewContainerConfig(args ...string) container.Config {
8689
} else if i := strings.IndexByte(utils.Config.Db.Image, ':'); config.VersionCompare(utils.Config.Db.Image[i+1:], "15.8.1.005") < 0 {
8790
env = append(env, "POSTGRES_INITDB_ARGS=--lc-collate=C.UTF-8")
8891
}
92+
// Apple containers mount volumes at the top-level target directory, which
93+
// conflicts with the default PGDATA location. Using a subdirectory avoids
94+
// the "initdb: directory is not empty" error on first start.
8995
if utils.UsesAppleContainerRuntime() {
9096
env = append(env, "PGDATA=/var/lib/postgresql/data/pgdata")
9197
}

internal/start/start.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ var (
178178
stopAppleAnalyticsForwarders = utils.StopAppleAnalyticsForwarders
179179
)
180180

181+
// Analytics log forwarding for Apple containers
182+
//
183+
// Apple containers do not expose a Docker-compatible log driver, so the
184+
// Vector `docker_logs` source cannot be used. Instead we:
185+
// 1. Spawn a per-container "forwarder" process (`apple-log-forwarder`
186+
// hidden CLI command) that tails `container logs --follow` and writes
187+
// JSONL to a host directory.
188+
// 2. Mount that directory into the Vector container.
189+
// 3. Configure Vector with a `file` source that reads the JSONL files.
190+
//
191+
// The forwarder PIDs are tracked in a temp directory so `supabase stop`
192+
// can clean them up.
181193
const (
182194
vectorSourceDockerLogs = "docker_logs"
183195
vectorSourceFile = "file"
@@ -205,6 +217,10 @@ func isPermanentError(err error) bool {
205217
return true
206218
}
207219

220+
// reconcileStaleProjectContainers removes stopped containers left over from a
221+
// previous run. This prevents name collisions when starting new containers,
222+
// which is especially important on Apple containers where stopped containers
223+
// are not automatically cleaned up.
208224
func reconcileStaleProjectContainers(ctx context.Context, projectId string) error {
209225
containers, err := listProjectContainers(ctx, projectId, true)
210226
if err != nil {
@@ -221,6 +237,10 @@ func reconcileStaleProjectContainers(ctx context.Context, projectId string) erro
221237
return nil
222238
}
223239

240+
// runtimeContainerHost returns the hostname that other containers should use
241+
// to reach the given container. Docker networks provide built-in DNS so the
242+
// container name works as a hostname. Apple containers do not have DNS within
243+
// their networks, so we must resolve the container's IP address instead.
224244
func runtimeContainerHost(ctx context.Context, containerId string, resolve bool) (string, error) {
225245
if !utils.UsesAppleContainerRuntime() || !resolve {
226246
return containerId, nil
@@ -1427,7 +1447,10 @@ EOF
14271447
started = append(started, utils.PoolerId)
14281448
}
14291449

1430-
// Start Kong after its Apple-runtime upstreams exist.
1450+
// Start Kong after its upstream services are running. Apple containers
1451+
// require IP resolution (no built-in DNS), so upstream containers must be
1452+
// alive before we can build Kong's declarative config. This ordering is
1453+
// harmless for Docker where DNS aliases resolve regardless of start order.
14311454
if isKongEnabled {
14321455
if err := startKong(ctx, KongDependencies{
14331456
Gotrue: isAuthEnabled,

internal/start/templates/kong.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ services:
112112
config:
113113
add:
114114
headers:
115+
# Apple containers resolve upstreams by IP, so the original Host
116+
# header is an IP address. Realtime requires the tenant ID as the
117+
# Host to route requests correctly.
115118
- "Host: {{ .RealtimeTenantId }}"
116119
replace:
117120
querystring:

internal/utils/apple_container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ func appleRemoveVolume(ctx context.Context, volumeName string, force bool) error
127127

128128
func appleRemoveVolumeWithRun(ctx context.Context, volumeName string, force bool, run func(context.Context, ...string) (string, error)) error {
129129
args := []string{"volume", "delete"}
130+
// Apple container CLI does not support force-delete for volumes.
130131
_ = force
131132
args = append(args, volumeName)
132133
if _, err := run(ctx, args...); err != nil {

internal/utils/runtime.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
"github.com/go-errors/errors"
1717
)
1818

19+
// healthcheckLabel stores the container's health-check command as a
20+
// base64-encoded JSON array inside a label. Apple containers do not support
21+
// native health-checks, so the CLI runs the check itself via `container exec`.
1922
const healthcheckLabel = "com.supabase.cli.healthcheck"
2023

2124
type ContainerMount struct {
@@ -100,6 +103,14 @@ func decodeHealthcheck(encoded string) ([]string, error) {
100103
return test, nil
101104
}
102105

106+
// The runtime dispatcher functions below use a simple if/else pattern rather
107+
// than an interface because:
108+
// - There are only two runtimes (Docker, Apple Container).
109+
// - Each Apple implementation is a thin wrapper around the `container` CLI,
110+
// keeping the logic co-located and easy to follow.
111+
// - An interface would require threading a runtime instance through many
112+
// call sites that currently use package-level helpers.
113+
103114
func DockerStart(ctx context.Context, config container.Config, hostConfig container.HostConfig, networkingConfig network.NetworkingConfig, containerName string) (string, error) {
104115
if UsesAppleContainerRuntime() {
105116
return appleStart(ctx, config, hostConfig, networkingConfig, containerName)

0 commit comments

Comments
 (0)