Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Slim-image runtime contracts that still differ from docker.io. Spec builders
* switch on {@link usesSlimImageRuntime} so flag-off stays byte-identical even
* if a caller passes a ghcr-shaped override.
* if a caller passes a slim-shaped override.
*
* Slim auth, storage, Vector, and the Elixir images (realtime, analytics,
* pooler) ship BusyBox wget — not GNU wget and not curl. BusyBox documents
Expand Down
30 changes: 30 additions & 0 deletions apps/cli/src/command-internal/docker-image-resolve.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,36 @@ describe("makeDockerImageResolver", () => {
}),
);

it.effect("pulls a slim image from ECR Public once GHCR keeps rate-limiting it", () =>
Effect.gen(function* () {
const originalWrite = globalThis.process.stderr.write.bind(globalThis.process.stderr);
globalThis.process.stderr.write = (() => true) as typeof globalThis.process.stderr.write;

try {
const digest = "sha256:8fb7ae7cd0d8121c460c8756d7f2791ce9a19c3331b46987456dfa4f8b45d701";
const slim = `ghcr.io/supabase/cli/postgres:17.11.0.004-r1@${digest}`;
const mirror = `public.ecr.aws/supabase/cli/postgres:17.11.0.004-r1@${digest}`;
const throttled = {
exitCode: 1,
stderr: "Error response from daemon: toomanyrequests: retry-after: 173.687µs",
};
const mock = mockSpawner([throttled, throttled, throttled, { exitCode: 0 }]);
const fiber = yield* makeDockerImageResolver(mock.spawner)(slim).pipe(
Effect.forkChild({ startImmediately: true }),
);

yield* TestClock.adjust("4 seconds");
yield* TestClock.adjust("8 seconds");
const image = yield* Fiber.join(fiber);

expect(mock.pulls).toEqual([slim, slim, slim, mirror]);
expect(image).toBe(mirror);
} finally {
globalThis.process.stderr.write = originalWrite;
}
}),
);

it.effect(
"does not inject a blank line before the banner when the child error is newline-terminated",
() =>
Expand Down
7 changes: 4 additions & 3 deletions apps/cli/src/command-internal/docker-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* overrides the default ECR mirror; `docker.io` returns the image unchanged, and any other value
* rewrites it to `<registry>/supabase/<last-path-segment>`. Callers that can retry pulls should
* use {@link getRegistryImageUrlCandidates} instead, which falls back through GHCR and the
* source image. Slim images ({@link isSlimImageRef}) skip every rewrite — there's no mirror to
* redirect them to.
* source image. Slim images ({@link isSlimImageRef}) skip these rewrites; their only fallback is
* the ECR Public mirror at the same tag and digest.
*/
import { slimImageMirrors } from "@supabase/stack/internal/artifacts";
import { Config, ConfigProvider, Effect, Option } from "effect";

import { isSlimImageRef } from "../shared/services/slim-images.ts";
Expand Down Expand Up @@ -67,7 +68,7 @@ export function getRegistryImageUrlCandidates(
projectEnvValues?: Readonly<Record<string, string>>,
): Effect.Effect<ReadonlyArray<string>, Config.ConfigError> {
if (isSlimImageRef(imageName)) {
return Effect.succeed([imageName]);
return Effect.succeed([imageName, ...slimImageMirrors(imageName)]);
}

return registryOverride(projectEnvValues).pipe(
Expand Down
10 changes: 6 additions & 4 deletions apps/cli/src/command-internal/docker-registry.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,14 @@ describe("getRegistryImageUrl", () => {
).toBe("merged.example/supabase/pg_prove:3.36");
});

// Published only under ghcr.io/supabase/cli; rewriting by last path segment would misroute it.
// Published under ghcr.io/supabase/cli; rewriting by last path segment would misroute it.
const SLIM_IMAGE = "ghcr.io/supabase/cli/postgres:17.6.1.165";
const SLIM_IMAGE_MIRROR = "public.ecr.aws/supabase/cli/postgres:17.6.1.165";

it("leaves a slim image unrewritten, whatever the registry override says", () => {
for (const registry of [undefined, "public.ecr.aws", "docker.io", "my.mirror.example"]) {
expect(resolveImage(registry, SLIM_IMAGE)).toBe(SLIM_IMAGE);
expect(resolveImage(registry, SLIM_IMAGE_MIRROR)).toBe(SLIM_IMAGE_MIRROR);
}
expect(
resolveImage(undefined, SLIM_IMAGE, {
Expand All @@ -116,15 +118,15 @@ describe("getRegistryImageUrl", () => {
).toBe(SLIM_IMAGE);
});

it("plans a single pull candidate for a slim image", () => {
it("plans the slim image, then its ECR Public mirror, whatever the registry override says", () => {
for (const registry of [undefined, "public.ecr.aws", "docker.io", "my.mirror.example"]) {
expect(resolveCandidates(registry, SLIM_IMAGE)).toEqual([SLIM_IMAGE]);
expect(resolveCandidates(registry, SLIM_IMAGE)).toEqual([SLIM_IMAGE, SLIM_IMAGE_MIRROR]);
}
expect(
resolveCandidates(undefined, SLIM_IMAGE, {
SUPABASE_INTERNAL_IMAGE_REGISTRY: "my.mirror.example",
}),
).toEqual([SLIM_IMAGE]);
).toEqual([SLIM_IMAGE, SLIM_IMAGE_MIRROR]);
});

it("still rewrites the non-slim ghcr.io/supabase namespace", () => {
Expand Down
26 changes: 13 additions & 13 deletions apps/cli/src/commands/db/dump/SIDE_EFFECTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ image), to stdout or `--file`.

## Environment Variables

| Variable | Purpose |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SUPABASE_DB_PASSWORD` (`DB_PASSWORD` viper key; `--password`/`-p` overrides) | remote DB password |
| `SUPABASE_ACCESS_TOKEN` | `--linked` auth |
| `SUPABASE_DB_ORIOLEDB_VERSION` | overrides `db.orioledb_version`, selecting the OrioleDB `pg_dump` image |
| `SUPABASE_EXPERIMENTAL_ORIOLEDB_VERSION` | deprecated alias of `SUPABASE_DB_ORIOLEDB_VERSION`, used only while that is unset; when set, stderr gets `WARN: SUPABASE_EXPERIMENTAL_ORIOLEDB_VERSION is deprecated and will be removed in v3, use SUPABASE_DB_ORIOLEDB_VERSION instead.` |
| `BITBUCKET_CLONE_DIR` | clears the `label:disable` security option that mounted TLS files add |
| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | rewrite the pg image registry for compose dumps; stack dumps use the catalog image pin unchanged |
| `SUPABASE_USE_SLIM_IMAGES` | resolve the current Postgres pin from the slim `ghcr.io/supabase/cli` builds (`true`/`1` enable); majors 13/15 use `15.14.1.167` when the flag is on; historical pins, PG14, OrioleDB tags the catalog does not pin, and flag-off `15.8.1.085` stay on docker.io |
| `DOCKER_HOST` | docker daemon endpoint |
| `MSYSTEM`, `TERM_PROGRAM` | suppress the piped-stdout non-ASCII warning in MSYS/mintty sessions |
| `PG*` libpq | linked-project TLS, timeout, service; see `db push`. An invalid value fails before `pg_dump` runs; a remote target also hands them to `pg_dump` (see Notes) |
| `SSL_CERT_FILE`, `SSL_CERT_DIR` | a native `pg_dump` on libpq 16+ keeps `sslrootcert=system` when either is set; libpq 15 uses only `SSL_CERT_FILE` |
| Variable | Purpose |
| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SUPABASE_DB_PASSWORD` (`DB_PASSWORD` viper key; `--password`/`-p` overrides) | remote DB password |
| `SUPABASE_ACCESS_TOKEN` | `--linked` auth |
| `SUPABASE_DB_ORIOLEDB_VERSION` | overrides `db.orioledb_version`, selecting the OrioleDB `pg_dump` image |
| `SUPABASE_EXPERIMENTAL_ORIOLEDB_VERSION` | deprecated alias of `SUPABASE_DB_ORIOLEDB_VERSION`, used only while that is unset; when set, stderr gets `WARN: SUPABASE_EXPERIMENTAL_ORIOLEDB_VERSION is deprecated and will be removed in v3, use SUPABASE_DB_ORIOLEDB_VERSION instead.` |
| `BITBUCKET_CLONE_DIR` | clears the `label:disable` security option that mounted TLS files add |
| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | rewrite the pg image registry for compose dumps; stack dumps use the catalog image pin unchanged |
| `SUPABASE_USE_SLIM_IMAGES` | resolve the current Postgres pin from the slim `ghcr.io/supabase/cli` builds (`true`/`1` enable); majors 13/15 use `15.14.1.167` when the flag is on; historical pins, PG14, OrioleDB tags the catalog does not pin, and flag-off `15.8.1.085` stay on docker.io; a failed GHCR pull falls back to the same tag and digest on `public.ecr.aws/supabase/cli` |
| `DOCKER_HOST` | docker daemon endpoint |
| `MSYSTEM`, `TERM_PROGRAM` | suppress the piped-stdout non-ASCII warning in MSYS/mintty sessions |
| `PG*` libpq | linked-project TLS, timeout, service; see `db push`. An invalid value fails before `pg_dump` runs; a remote target also hands them to `pg_dump` (see Notes) |
| `SSL_CERT_FILE`, `SSL_CERT_DIR` | a native `pg_dump` on libpq 16+ keeps `sslrootcert=system` when either is set; libpq 15 uses only `SSL_CERT_FILE` |

## Exit Codes

Expand Down
Loading
Loading