diff --git a/.github/workflows/self-host-tests-smoke.yml b/.github/workflows/self-host-tests-smoke.yml index 455d68c81fe..ac7e0eecc60 100644 --- a/.github/workflows/self-host-tests-smoke.yml +++ b/.github/workflows/self-host-tests-smoke.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - config: [default, logs, envoy, rustfs, envoy-rustfs] + config: [default, logs, kong, rustfs, kong-rustfs] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -51,12 +51,12 @@ jobs: yq -i '.services.supavisor.environment.RLIMIT_NOFILE=1024' docker-compose.yml if [ "${{ matrix.config }}" = "logs" ]; then docker compose -f docker-compose.yml -f docker-compose.logs.yml up --quiet-pull --wait --wait-timeout 180 - elif [ "${{ matrix.config }}" = "envoy" ]; then - docker compose -f docker-compose.yml -f docker-compose.envoy.yml up --quiet-pull --wait --wait-timeout 180 + elif [ "${{ matrix.config }}" = "kong" ]; then + docker compose -f docker-compose.yml -f docker-compose.kong.yml up --quiet-pull --wait --wait-timeout 180 elif [ "${{ matrix.config }}" = "rustfs" ]; then docker compose -f docker-compose.yml -f docker-compose.rustfs.yml up --quiet-pull --wait --wait-timeout 180 - elif [ "${{ matrix.config }}" = "envoy-rustfs" ]; then - docker compose -f docker-compose.yml -f docker-compose.envoy.yml -f docker-compose.rustfs.yml up --quiet-pull --wait --wait-timeout 180 + elif [ "${{ matrix.config }}" = "kong-rustfs" ]; then + docker compose -f docker-compose.yml -f docker-compose.kong.yml -f docker-compose.rustfs.yml up --quiet-pull --wait --wait-timeout 180 else docker compose up --quiet-pull --wait --wait-timeout 180 fi diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index d04c48cb3b3..6f13f250734 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -3085,7 +3085,7 @@ export const self_hosting: NavMenuConstant = { { name: 'Overview', url: '/guides/self-hosting' }, { name: 'Deploy with Docker', url: '/guides/self-hosting/docker' }, { name: 'Configure new API keys', url: '/guides/self-hosting/self-hosted-auth-keys' }, - { name: 'Enable Envoy API Gateway', url: '/guides/self-hosting/self-hosted-envoy' }, + { name: 'Learn about API Gateway', url: '/guides/self-hosting/self-hosted-envoy' }, { name: 'Add Reverse Proxy with HTTPS', url: '/guides/self-hosting/self-hosted-proxy-https', diff --git a/apps/docs/content/guides/self-hosting/copy-from-platform-s3.mdx b/apps/docs/content/guides/self-hosting/copy-from-platform-s3.mdx index 370c76591f3..2b4e050f4e8 100644 --- a/apps/docs/content/guides/self-hosting/copy-from-platform-s3.mdx +++ b/apps/docs/content/guides/self-hosting/copy-from-platform-s3.mdx @@ -123,7 +123,7 @@ For large migrations, consider adding `--transfers 4` to increase parallelism, o -## Verify +## Verify the copy Compare object counts between source and destination: diff --git a/apps/docs/content/guides/self-hosting/enable-mcp.mdx b/apps/docs/content/guides/self-hosting/enable-mcp.mdx index 13b0ceb71db..b6d95588230 100644 --- a/apps/docs/content/guides/self-hosting/enable-mcp.mdx +++ b/apps/docs/content/guides/self-hosting/enable-mcp.mdx @@ -29,20 +29,9 @@ When connecting via an SSH tunnel to the Studio Docker container, the source IP scrollable size="small" type="underlined" -defaultActiveId="kong" +defaultActiveId="envoy" > - - -Determine the Docker bridge gateway IP on the host running your Supabase containers: - -```sh -docker inspect supabase-kong \ - --format '{{range .NetworkSettings.Networks}}{{println .Gateway}}{{end}}' -``` - - - Determine the Docker bridge gateway IP on the host running your Supabase containers: @@ -54,6 +43,17 @@ docker inspect supabase-envoy \ + + +Determine the Docker bridge gateway IP on the host running your Supabase containers: + +```sh +docker inspect supabase-kong \ + --format '{{range .NetworkSettings.Networks}}{{println .Gateway}}{{end}}' +``` + + + This command will output an IP address, e.g., `172.18.0.1`. @@ -64,53 +64,9 @@ This command will output an IP address, e.g., `172.18.0.1`. scrollable size="small" type="underlined" -defaultActiveId="kong" +defaultActiveId="envoy" > - - -Add the IP address you discovered to the Kong configuration by editing the following section in `./volumes/api/kong.yml`: - -1. Comment out the request-termination section -2. Remove the # symbols from the entire section starting with `- name: cors`, including `deny: []` -3. Add your local IP to the 'allow' list -4. **Preserve the existing indentation** - YAML is whitespace-sensitive and the config will fail to load if it changes -5. Your edited configuration should look like the example below: - -```yaml name=volumes/api/kong.yml -## MCP endpoint - local access -- name: mcp - _comment: 'MCP: /mcp -> http://studio:3000/api/mcp (local access)' - url: http://studio:3000/api/mcp - routes: - - name: mcp - strip_path: true - paths: - - /mcp - plugins: - # Block access to /mcp by default - #- name: request-termination - # config: - # status_code: 403 - # message: "Access is forbidden." - # Enable local access (danger zone!) - # 1. Comment out the 'request-termination' section above - # 2. Uncomment the entire section below, including 'deny' - # 3. Add your local IPs to the 'allow' list - - name: cors - - name: ip-restriction - config: - allow: - - 127.0.0.1 - - ::1 - # Add your Docker bridge gateway IP below - - 172.18.0.1 - # Do not remove deny! - deny: [] -``` - - - Add the IP address you discovered to the Envoy configuration by editing the `/mcp` route in `./volumes/api/envoy/lds.template.yaml`: @@ -177,39 +133,60 @@ Add the IP address you discovered to the Envoy configuration by editing the `/mc + + +Add the IP address you discovered to the Kong configuration by editing the following section in `./volumes/api/kong.yml`: + +1. Comment out the request-termination section +2. Remove the # symbols from the entire section starting with `- name: cors`, including `deny: []` +3. Add your local IP to the 'allow' list +4. **Preserve the existing indentation** - YAML is whitespace-sensitive and the config will fail to load if it changes +5. Your edited configuration should look like the example below: + +```yaml name=volumes/api/kong.yml +## MCP endpoint - local access +- name: mcp + _comment: 'MCP: /mcp -> http://studio:3000/api/mcp (local access)' + url: http://studio:3000/api/mcp + routes: + - name: mcp + strip_path: true + paths: + - /mcp + plugins: + # Block access to /mcp by default + #- name: request-termination + # config: + # status_code: 403 + # message: "Access is forbidden." + # Enable local access (danger zone!) + # 1. Comment out the 'request-termination' section above + # 2. Uncomment the entire section below, including 'deny' + # 3. Add your local IPs to the 'allow' list + - name: cors + - name: ip-restriction + config: + allow: + - 127.0.0.1 + - ::1 + # Add your Docker bridge gateway IP below + - 172.18.0.1 + # Do not remove deny! + deny: [] +``` + + + ### Step 3: Restart API gateway After you've added the local IP address as above, restart your gateway: - - - - -```sh -sh run.sh restart kong -``` - - - - - ```sh sh run.sh restart api-gw ``` -This assumes the Envoy override is already active (`sh run.sh config add envoy`). - - - - - ### Step 4: Create the SSH tunnel From your local machine, create an SSH tunnel to your Supabase host: @@ -268,7 +245,6 @@ Start your MCP client (Claude Code, Cursor, etc.) and verify access to the MCP t If you are unable to connect to the MCP server: -1. Update Kong configuration file to the [latest version](https://github.com/supabase/supabase/blob/master/docker/volumes/api/kong.yml) and edit carefully -2. Confirm the Docker bridge gateway IP is correctly added in `./volumes/api/kong.yml` -3. Check Kong's logs for errors: `docker compose logs kong` -4. Make sure your SSH tunnel is active +1. Confirm the Docker bridge gateway IP is correctly added to the API gateway configuration +2. Check the API gateway's logs for errors: `docker compose logs api-gw` +3. Make sure your SSH tunnel is active diff --git a/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx b/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx index 1ced340ad25..9f98e695048 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx @@ -11,13 +11,12 @@ You can configure self-hosted Supabase to use the [new API keys](/docs/guides/ge {/* supa-mdx-lint-disable-next-line Rule003Spelling */} - Complete the [Docker setup guide](/docs/guides/self-hosting/docker) so that `JWT_SECRET`, `ANON_KEY`, and `SERVICE_ROLE_KEY` are set in your `.env` file. [Quick start (Linux)](/docs/guides/self-hosting/docker#quick-start-linux) handles this automatically; the manual path runs [`generate-keys.sh`](/docs/guides/self-hosting/docker#generate-keys-and-secrets). -- If you are upgrading an existing self-hosted Supabase environment, make sure to check the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md) and add/update the following files: +- If you are upgrading from a legacy self-hosted Supabase environment, make sure to check the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md#2026-03-16) and [add/update](/docs/guides/self-hosting/updating) the following files: - `.env.example` (merge new sections into your `.env` file) - `docker-compose.yml` - `utils/add-new-auth-keys.sh` - `utils/rotate-new-api-keys.sh` - - `volumes/api/kong-entrypoint.sh` - - `volumes/api/kong.yml` + - `volumes/api/envoy/*` ## Adding the new keys @@ -27,7 +26,7 @@ From your project directory where you have `docker-compose.yml`: sh utils/add-new-auth-keys.sh --update-env ``` -This generates new configuration environment variables and writes them to `.env`. Without `--update-env`, the script prints the values and prompts you interactively. +This generates new configuration environment variables and writes them to `.env`. Omit `--update-env` to review and confirm the changes interactively. @@ -35,7 +34,7 @@ The script reads `JWT_SECRET` from `.env` and includes it as a symmetric key ins -The following configuration should be uncommented in the `.env` file for the new authentication to work correctly: +In addition, the following configuration is uncommented automatically by the script for the new authentication to work correctly: ```yaml name=docker-compose.yml auth: @@ -57,6 +56,11 @@ storage: environment: # JWKS for token verification (EC public + legacy symmetric) JWT_JWKS: ${JWT_JWKS:-{"keys":[]}} + +functions: + environment: + # JWKS for token verification (EC public + legacy symmetric). + SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}} ``` @@ -124,7 +128,7 @@ New variables default to empty values in `.env.example`. When empty, the API gat | `SUPABASE_PUBLISHABLE_KEY` | Opaque | **New:** Short random key with checksum. Replaces `ANON_KEY` for **client-side** use. | | `SUPABASE_SECRET_KEY` | Opaque | **New:** Short random key with checksum. Replaces `SERVICE_ROLE_KEY` for **server-side** use. | | `JWT_KEYS` | JSON array | **New:** JSON array of signing JWKs containing the new asymmetric key pair and the legacy symmetric key. Used by Auth to sign tokens. | -| `JWT_JWKS` | JWKS (JSON) | **New:** Contains the new public key and the legacy symmetric key. Used by PostgREST, Realtime, and Storage to verify tokens. | +| `JWT_JWKS` | JWKS (JSON) | **New:** Contains the new public key and the legacy symmetric key. Used by PostgREST, Realtime, Storage, and Functions. | ### Differences from the Supabase platform @@ -187,93 +191,16 @@ Regenerating asymmetric keys invalidates all ES256 user sessions. Plan a mainten ## How it works -Below are a few notes on the details of the new authentication architecture. - -### What client SDK sends - -Every request via `supabase-js` includes two headers: +Requests via `supabase-js` include two headers for every service except Edge Functions: - `apikey` - the API key (`sb_` or legacy JWT) - `Authorization` - when unauthenticated, the client SDK copies the API key here (`Bearer sb_publishable_xxx` or `Bearer eyJ...`). When authenticated, this contains the user session JWT minted by Auth. For **Realtime WebSocket** connections, the API key is sent as a `?apikey=` query parameter in the upgrade URL instead of an `apikey` header. -**Storage** and **Edge Functions** accept requests without an API key. These services handle their own authentication. +**Storage** and **Edge Functions** also accept requests without an API key. These services handle their own authentication. -### Kong API gateway routing - -Kong is configured with two consumers that each accept both the legacy and new API keys: - -```yaml name=volumes/api/kong.yml -consumers: - - username: anon - keyauth_credentials: - - key: $SUPABASE_ANON_KEY # legacy HS256 JWT (ANON_KEY) - - key: $SUPABASE_PUBLISHABLE_KEY # new opaque key (omitted when not configured) - - username: service_role - keyauth_credentials: - - key: $SUPABASE_SERVICE_KEY # legacy HS256 JWT (SERVICE_ROLE_KEY) - - key: $SUPABASE_SECRET_KEY # new opaque key (omitted when not configured) -``` - -When new API keys have not been added yet, the `kong-entrypoint.sh` script removes the empty credential entries before Kong loads the config. - -To assist with the authorization flows a specialized configuration in `kong.yml` substitutes internal, gateway-level-only pre-signed JWTs for `sb_publishable` and `sb_secret` API keys. These pre-signed JWTs are also auto-configured in `.env` but **should not** be used in any application code. - -| Route | Service | API key required | Header substitution | -| -------------------------- | -------------------- | ---------------- | ------------------- | -| `/auth/v1/*` | Auth | Yes | `Authorization` | -| `/rest/v1/*` | PostgREST | Yes | `Authorization` | -| `/graphql/v1` | PostgREST | Yes | `Authorization` | -| `/realtime/v1/api/tenants` | Realtime (REST) | Denied (blocked) | - | -| `/realtime/v1/api/openapi` | Realtime (REST) | Denied (blocked) | - | -| `/realtime/v1/api/*` | Realtime (REST) | Yes | `Authorization` | -| `/realtime/v1/*` | Realtime (WebSocket) | Yes | `x-api-key` | -| `/storage/v1/*` | Storage | No | `Authorization` | -| `/functions/v1/*` | Edge Functions | No | - | - -### Request flows - -The API gateway (Kong) configuration has the logic to decide what `Authorization` header the upstream service, such as Auth, receives. The logic handles two cases: requests that only carry an API key (no user session), and requests that carry a user session JWT. - -#### Unauthenticated requests (API key only, no user session JWT) - -When the client sends only an `apikey` header with the API key (no `Authorization` header), or also the API key duplicated in `Authorization` by `supabase-js`: - -1. The client sends `apikey: sb_publishable_xxx` (or legacy `apikey: eyJ...`). -2. The API gateway checks the key and identifies the consumer (`anon` or `service_role`). -3. The API gateway inspects the `Authorization` header. Since it is either absent or starts with `Bearer sb_` (an opaque key, not a session JWT), the plugin replaces it: - - **The new `sb_` key:** `Authorization` header is set to the internal pre-signed ES256 JWT that corresponds to the role. - - **The Legacy JWT key:** `Authorization` header is set to the legacy HS256 JWT (the `apikey` value is copied as-is). -4. The upstream service receives a valid JWT in `Authorization` and verifies it using `JWT_JWKS` (or `JWT_SECRET`). - -#### Authenticated requests (user session JWT) - -When the client has previously signed in through Auth and has a valid user session JWT token: - -1. The client sends `Authorization: Bearer eyJ...` (a JWT session token from Auth) alongside `apikey: sb_publishable_xxx` (or legacy `apikey: eyJ...`). -2. The API gateway checks the API key and identifies the consumer. -3. The API gateway inspects the `Authorization` header. Since it exists and does **not** start with `Bearer sb_` (it's a real JWT, not an `sb_` API key), the plugin **passes it through unchanged**. This works the same way regardless of whether the `apikey` is a new `sb_` key or a legacy JWT - the gateway only looks at the `Authorization` header to decide whether a user session is present. -4. The upstream service verifies the session JWT. If Auth signed it with ES256 (when `JWT_KEYS` is configured), verification uses the EC public key. If Auth signed it with HS256 (legacy), verification uses the symmetric key. Both keys are available in `JWT_JWKS`. - -The `request-transformer` expression in `kong.yml` implements this as a single Lua conditional: - -```lua --- Pseudocode for the Authorization header logic: -if authorization exists AND does not start with "Bearer sb_" then - -- User session JWT: pass through unchanged - keep authorization -elseif apikey matches secret key then - -- Replace with pre-signed service_role ES256 JWT - set authorization = "Bearer " -elseif apikey matches publishable key then - -- Replace with pre-signed anon ES256 JWT - set authorization = "Bearer " -else - -- Legacy JWT key: copy apikey as authorization - set authorization = apikey -end -``` +For details on how the API gateway routes and authenticates requests, see the [Envoy API Gateway guide](/docs/guides/self-hosting/self-hosted-envoy#authentication). ## Additional resources @@ -287,3 +214,4 @@ On GitHub: - [Upcoming changes to Supabase API Keys (Discussion #29260)](https://github.com/orgs/supabase/discussions/29260) - [Supabase Auth: Asymmetric Keys support (Discussion #29289)](https://github.com/orgs/supabase/discussions/29289) +- [Self-hosted Supabase: Envoy becomes the default API gateway (Discussion #48048)](https://github.com/orgs/supabase/discussions/48048) diff --git a/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx b/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx index 68b2d147aec..150cb4a7c54 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx @@ -4,39 +4,17 @@ description: 'Architecture and configuration of the Envoy API gateway for self-h subtitle: 'Architecture and configuration of the Envoy API gateway for self-hosted Supabase.' --- -Self-hosted Supabase ships with an optional [Envoy](https://www.envoyproxy.io/)-based API gateway. It accepts incoming client requests, routes them to internal services (Auth, PostgREST, Realtime, Storage, Edge Functions, postgres-meta, Studio), and enforces API key authentication by translating opaque `sb_` keys into the internal credentials used by those services. +Self-hosted Supabase uses an [Envoy](https://www.envoyproxy.io/)-based API gateway by default. It accepts incoming client requests, routes them to internal services (Auth, PostgREST, Realtime, Storage, Edge Functions, postgres-meta, Studio), and enforces API key authentication by translating opaque `sb_` keys into the internal credentials used by those services. This guide explains the architecture, configuration layout, and security posture of the Envoy gateway for operators who want to understand or customize it. It is not an Envoy tutorial - for reference on filters, routes, and clusters, see the [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/). -## Before you begin - -- Complete the [Self-Hosting with Docker](/docs/guides/self-hosting/docker) setup -- To enable opaque `sb_` key translation, see [New API Keys and Asymmetric Authentication](/docs/guides/self-hosting/self-hosted-auth-keys) - {/* supa-mdx-lint-disable-next-line Rule001HeadingCase */} -## Enabling the Envoy gateway +## Using the gateway -The Envoy gateway is provided as a Docker Compose override. +Envoy is the default API gateway and runs automatically when you start the stack - no extra configuration is required. - - -If your stack is already running from the initial setup, bring it down first with `sh run.sh stop`. - - - -Enable the Envoy override, then start the stack: - -```sh -sh run.sh config add envoy -sh run.sh start -``` - -The override disables the default Kong gateway and starts Envoy on the same port (default `8000`). It also reconfigures the Functions service to wait for Envoy via a dependency. - -Envoy is registered as the `api-gw` service and also exposes `kong` as a network alias; the base Kong service likewise exposes `api-gw`. Either hostname resolves to whichever gateway is currently active, so internal configs that hardcode `kong:8000` (for example, in Edge Functions or Studio) keep working without changes. - -### Verify +Envoy is registered as the `api-gw` service and also exposes `envoy` and `kong` as network aliases to help transition from legacy configurations. Confirm the gateway is routing requests and enforcing API keys: @@ -231,16 +209,15 @@ If you customize the `cors:` block in `lds.template.yaml` to enable `allow_crede The gateway is configured with these production-oriented settings: -| Setting | Purpose | -| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `normalize_path: true`, `merge_slashes: true` | Prevents path-confusion bypass of RBAC prefix rules | -| `path_with_escaped_slashes_action: REJECT_REQUEST` | Rejects requests that contain URL-encoded slashes in the path | -| `use_remote_address: true` | Treats Envoy as an edge proxy: uses the peer connection IP as the trusted client address (rather than trusting client-supplied `X-Forwarded-For`) and strips untrusted `x-envoy-*` request headers | -| `headers_with_underscores_action: REJECT_REQUEST` | Blocks header smuggling attacks that exploit underscore-vs-hyphen normalization | -| `per_connection_buffer_limit_bytes: 32768` | Caps per-connection buffer memory to 32 KiB | -| `max_active_downstream_connections: 30000` | Overload manager limit on total downstream connections | -| Admin interface bound to `127.0.0.1:9901` | The admin API is reachable only from inside the container, not from other containers or the host | -| Image pinned to `envoyproxy/envoy:v1.37.2` (or newer) | Includes published security patches for Envoy 1.37.x | +| Setting | Purpose | +| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `normalize_path: true`, `merge_slashes: true` | Prevents path-confusion bypass of RBAC prefix rules | +| `path_with_escaped_slashes_action: REJECT_REQUEST` | Rejects requests that contain URL-encoded slashes in the path | +| `use_remote_address: true` | Treats Envoy as an edge proxy: uses the peer connection IP as the trusted client address (rather than trusting client-supplied `X-Forwarded-For`) and strips untrusted `x-envoy-*` request headers | +| `headers_with_underscores_action: REJECT_REQUEST` | Blocks header smuggling attacks that exploit underscore-vs-hyphen normalization | +| `per_connection_buffer_limit_bytes: 32768` | Caps per-connection buffer memory to 32 KiB | +| `max_active_downstream_connections: 30000` | Overload manager limit on total downstream connections | +| Admin interface bound to `127.0.0.1:9901` | The admin API is reachable only from inside the container, not from other containers or the host | @@ -260,7 +237,7 @@ All routing, filter, and cluster changes are made in the YAML files under `./vol - **Adding or modifying routes.** Edit `lds.template.yaml`. Routes are ordered - place new routes before the catch-all `/` route to ensure they match. Keep per-route `basic_auth: disabled` for API routes and set an appropriate RBAC override if the route should bypass the global policy. - **Adding a new upstream service.** Add a cluster definition to `cds.yaml` with the service's DNS name and port, then reference it from a route's `cluster:` field. -- **Adding a new environment variable.** Placeholders in the template use the `${VAR_NAME}` form. If you add a placeholder, update both `docker-compose.envoy.yml` (to pass the variable into the container) and `docker-entrypoint.sh` (to substitute it with `sed`). +- **Adding a new environment variable.** Placeholders in the template use the `${VAR_NAME}` form. If you add a placeholder, update both the `api-gw` service in `docker-compose.yml` (to pass the variable into the container) and `docker-entrypoint.sh` (to substitute it with `sed`). - **Applying changes.** Envoy reads the rendered `lds.yaml` from the filesystem. Configuration changes require restarting the container so the entrypoint re-renders the template: ```sh @@ -323,3 +300,4 @@ The access log format is a standard combined log with the request method, origin - [New API Keys and Asymmetric Authentication](/docs/guides/self-hosting/self-hosted-auth-keys) - Background on opaque keys and asymmetric JWTs - [Configure Reverse Proxy and HTTPS](/docs/guides/self-hosting/self-hosted-proxy-https) - Caddy or Nginx in front of the gateway - [Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/) - Filter, route, and cluster reference +- [Self-hosted Supabase: Envoy becomes the default API gateway (Discussion #48048)](https://github.com/orgs/supabase/discussions/48048) diff --git a/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx b/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx index 391e61ace7a..1a806cb00dc 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx @@ -81,8 +81,7 @@ functions: env_file: - .env.functions environment: - JWT_SECRET: ${JWT_SECRET} - SUPABASE_URL: http://kong:8000 + # ... existing variables ... ``` @@ -106,9 +105,7 @@ functions: environment: # Custom variables MY_CUSTOM_VAR: ${MY_CUSTOM_VAR} - # Required variables - JWT_SECRET: ${JWT_SECRET} - SUPABASE_URL: http://kong:8000 + # ... existing variables ... ``` Then define `MY_CUSTOM_VAR` in your main `.env` file, or specify the value directly. @@ -127,7 +124,7 @@ The functions service is pre-configured with the following environment variables | Variable | Value | Purpose | | --------------------------- | --------------------------------- | ------------------------------------------------- | -| `SUPABASE_URL` | `http://kong:8000` | Internal API gateway URL | +| `SUPABASE_URL` | `http://api-gw:8000` | Internal API gateway URL | | `SUPABASE_PUBLIC_URL` | `http(s)://` | Base URL for accessing Supabase from the Internet | | `JWT_SECRET` | `your-jwt-secret` | Legacy symmetric encryption key for JWTs | | `SUPABASE_ANON_KEY` | `your-anon-key` | Client-side API key (`anon` role). | @@ -135,6 +132,7 @@ The functions service is pre-configured with the following environment variables | `SUPABASE_DB_URL` | `postgresql://...` | Postgres connection string | | `SUPABASE_PUBLISHABLE_KEYS` | `{"default":"sb_publishable_...}` | New publishable API key | | `SUPABASE_SECRET_KEYS` | `{"default":"sb_secret_...}` | New secret API key | +| `SUPABASE_JWKS` | `{"keys":[{...}]}` | JWKS used to verify JWTs issued by Auth | Here's an example function that queries a table using `@supabase/supabase-js`: diff --git a/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx index 6c1dea4ddc1..127cf77f411 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx @@ -33,12 +33,6 @@ If you already run [HAProxy](https://www.haproxy.com/), [Traefik](https://traefi - - -Envoy is an optional [API gateway](/docs/guides/self-hosting/self-hosted-envoy), enabled via the `docker-compose.envoy.yml` override. If you already run Envoy instead of Kong, edit `docker-compose.caddy.yml` or `docker-compose.nginx.yml` to comment out the `kong:` block and uncomment the `api-gw:` block (and the matching `depends_on` entry) so the reverse proxy sits in front of Envoy. - - - ### Step 1: Update environment variables Update the URL configuration in your `.env` file to use your HTTPS domain: @@ -51,7 +45,7 @@ SITE_URL=https:// -`` is the URL of your own frontend application (where users land after signing in) - not your Supabase instance. It's often a different domain, and possibly a different service entirely, from `` used above. +`` is the URL of your own frontend application where users land after signing in - not your Supabase instance. @@ -126,7 +120,7 @@ Self-signed certificates trigger browser warnings and are rejected by most OAuth -For development or internal networks where you cannot use Let's Encrypt, here's how you can configure Kong (the current default API gateway) to serve HTTPS directly using self-signed certificates. +For development or internal networks where you cannot use Let's Encrypt, here's how you can configure the legacy Kong API gateway to serve HTTPS directly using self-signed certificates. This requires the Kong override (`sh run.sh config add kong`); the default Envoy gateway does not terminate TLS, so use Caddy or Nginx above instead. ### Step 1: Generate a self-signed certificate @@ -147,20 +141,23 @@ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ Comment out Kong's **HTTP** port mapping in `docker-compose.yml`: -```yaml name=docker-compose.yml -kong: +```yaml name=docker-compose.kong.yml +api-gw: # ... - ports: - #- ${KONG_HTTP_PORT}:8000/tcp + # prettier-ignore + ports: !override + #- ${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp + - ${KONG_HTTPS_PORT:-8443}:8443/tcp ``` Uncomment the certificate volume mounts and SSL environment variables in `docker-compose.yml`: -```yaml name=docker-compose.yml -kong: +```yaml name=docker-compose.kong.yml +api-gw: # ... existing configuration ... - volumes: + volumes: !override - ./volumes/api/kong.yml:/home/kong/temp.yml:ro,z + - ./volumes/api/kong-entrypoint.sh:/home/kong/kong-entrypoint.sh:ro,z - ./volumes/api/server.crt:/home/kong/server.crt:ro - ./volumes/api/server.key:/home/kong/server.key:ro environment: diff --git a/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx b/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx index 8d9c392591c..ff426538635 100644 --- a/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx +++ b/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx @@ -154,7 +154,7 @@ storage: REGION: your-region ``` -## Verify +## Verify the setup {/* supa-mdx-lint-disable-next-line Rule003Spelling */} diff --git a/apps/docs/content/guides/self-hosting/updating.mdx b/apps/docs/content/guides/self-hosting/updating.mdx index 1a03a879965..e2e65874672 100644 --- a/apps/docs/content/guides/self-hosting/updating.mdx +++ b/apps/docs/content/guides/self-hosting/updating.mdx @@ -181,7 +181,8 @@ Long-running deployments are usually assembled from several upstream points over 4. Back up your database separately - `update.sh` backs up configuration only. 5. Apply the update, then resolve conflicts. Most conflicts will be in "vendor files" you never meant to own - `run.sh`, `setup.sh`, `tests/*`, the override templates. For those, copy the new version as-is. The conflicts that need manual editing are usually in the compose configuration you deliberately changed. Your `.env` is never conflicted - `update.sh` appends new keys for you to review separately. The tool surfaces everything for you to triage. Refer to [Resolving conflicts](#resolving-conflicts) for more details. 6. Handle Postgres. Postgres 17 became the default in v0.6.0. If you are still on Postgres 15, do not recreate straight onto 17 - follow [Upgrade to Postgres 17](/docs/guides/self-hosting/postgres-upgrade-17), or pin Postgres 15 with the `docker-compose.pg15.yml` override. Read the [changelog](https://github.com/supabase/supabase/blob/master/docker/CHANGELOG.md) for the breaking changes across the range you are crossing. -7. Run `sh run.sh pull`, then `sh run.sh recreate`. +7. Upgrade the API gateway. Envoy became the default in v0.8.0, replacing Kong. If you customized `volumes/api/kong.yml` or the gateway service, use the Kong override to keep Kong; otherwise the merge switches you to Envoy. The gateway service is renamed from `kong` to `api-gw`, but the `kong` hostname alias still resolves. +8. Run `sh run.sh pull`, then `sh run.sh recreate`. diff --git a/docker/.env.example b/docker/.env.example index f1ffd734ce0..fe428f5091d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -341,7 +341,12 @@ GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER # API gateway ############ -# Kong configuration variables +# Host port the API gateway (Envoy by default) listens on. +API_GW_HTTP_PORT=8000 + +# Kong gateway override only (sh run.sh config add kong). KONG_HTTPS_PORT is +# Kong's built-in HTTPS listener; KONG_HTTP_PORT is kept as a fallback for +# API_GW_HTTP_PORT so existing .env files continue to work. KONG_HTTP_PORT=8000 KONG_HTTPS_PORT=8443 diff --git a/docker/README.md b/docker/README.md index 9d710607c9a..48c8220c72f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -25,7 +25,7 @@ The guide covers: This Docker Compose configuration includes the following services: - **[Studio](https://github.com/supabase/supabase/tree/master/apps/studio)** - A dashboard for managing your self-hosted Supabase project -- **[Kong](https://github.com/Kong/kong)** - Kong API gateway +- **[Envoy](https://www.envoyproxy.io/)** - API gateway (default; Kong is available as an optional override via `sh run.sh config add kong`) - **[Auth](https://github.com/supabase/auth)** - JWT-based authentication API for user sign-ups, logins, and session management - **[PostgREST](https://github.com/PostgREST/postgrest)** - Web server that turns your PostgreSQL database directly into a RESTful API - **[Realtime](https://github.com/supabase/realtime)** - Elixir server that listens to PostgreSQL database changes and broadcasts them over websockets diff --git a/docker/docker-compose.caddy.yml b/docker/docker-compose.caddy.yml index 35510081e5b..9f99ff3b2a8 100644 --- a/docker/docker-compose.caddy.yml +++ b/docker/docker-compose.caddy.yml @@ -1,22 +1,13 @@ services: - # By default, Kong is used as the API gateway and its ports/env are reset - # below so Caddy can terminate TLS in front of it. - # - # When running Envoy instead, e.g.: - # docker compose -f docker-compose.yml -f docker-compose.envoy.yml \ - # -f docker-compose.caddy.yml up -d - # comment out the `kong:` block below and uncomment the `api-gw:` block - # (and the matching `depends_on` entry further down) so Caddy sits in front - # of Envoy rather than Kong. - - #api-gw: - # ports: !reset [] - - kong: + # Caddy terminates TLS and forwards to the API gateway (api-gw) on port 8000, + # so the gateway's own host port binding is removed here. This works for + # either gateway, since the service is named api-gw in both cases. + api-gw: ports: !reset [] - environment: - KONG_PORT_MAPS: "443:8000,443:8443" + # When using the Kong override uncomment the following: + #environment: + # KONG_PORT_MAPS: "443:8000,443:8443" caddy: container_name: supabase-caddy @@ -27,9 +18,7 @@ services: - "443:443" - "443:443/udp" depends_on: - #api-gw: - # condition: service_healthy - kong: + api-gw: condition: service_healthy studio: condition: service_healthy diff --git a/docker/docker-compose.envoy.yml b/docker/docker-compose.envoy.yml index 8a2d706ffdb..93ff2b0cfbc 100644 --- a/docker/docker-compose.envoy.yml +++ b/docker/docker-compose.envoy.yml @@ -1,51 +1,15 @@ -# Envoy override for Kong -# Usage: docker compose -f docker-compose.yml -f docker-compose.envoy.yml up +# DEPRECATED: Envoy is now the default API gateway defined directly in +# docker-compose.yml, so this override is no longer needed and does nothing. +# +# This no-op shim is kept for one release cycle so existing COMPOSE_FILE +# entries referencing it do not break. Remove it from your configuration: +# +# sh run.sh config remove envoy +# +# To run Kong instead of Envoy, use the Kong override: +# +# sh run.sh config add kong +# +# This file will be removed in a future release. -services: - # Disable the original Kong service - kong: - profiles: - - disabled - - # Rewire dependencies that require Kong to Envoy - functions: - depends_on: !override - api-gw: - condition: service_healthy - - # Envoy API gateway - api-gw: - container_name: supabase-envoy - image: envoyproxy/envoy:v1.39.0 - restart: unless-stopped - ports: - - ${KONG_HTTP_PORT}:8000/tcp - volumes: - - ./volumes/api/envoy/envoy.yaml:/etc/envoy/envoy.yaml:ro - - ./volumes/api/envoy/cds.yaml:/etc/envoy/cds.yaml:ro - - ./volumes/api/envoy/lds.template.yaml:/etc/envoy/lds.template.yaml:ro - - ./volumes/api/envoy/docker-entrypoint.sh:/docker-entrypoint.sh:ro - depends_on: - studio: - condition: service_healthy - environment: - ANON_KEY: ${ANON_KEY} - SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY} - SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY:-} - SUPABASE_SECRET_KEY: ${SUPABASE_SECRET_KEY:-} - ANON_KEY_ASYMMETRIC: ${ANON_KEY_ASYMMETRIC:-} - SERVICE_ROLE_KEY_ASYMMETRIC: ${SERVICE_ROLE_KEY_ASYMMETRIC:-} - DASHBOARD_USERNAME: ${DASHBOARD_USERNAME} - DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} - entrypoint: ["/bin/sh", "/docker-entrypoint.sh"] - healthcheck: - # Using a TCP port check because this image does not include curl or wget. - test: ["CMD-SHELL", "timeout 1 bash -c ' is unaffected." ] + }, + "0.8.0": { + "breaking": true, + "gate": null, + "migration_guide_url": "https://github.com/orgs/supabase/discussions/48048", + "requires": [ + "Envoy is now the default API gateway, replacing Kong. The gateway service is renamed from 'kong' to 'api-gw' (container 'supabase-envoy'); the 'kong' network alias still resolves, so internal service references keep working.", + "If you customized volumes/api/kong.yml or the gateway service, enable the Kong override with 'sh run.sh config add kong' to keep running Kong; otherwise the merge switches you to Envoy." + ] } } diff --git a/docker/volumes/proxy/caddy/Caddyfile b/docker/volumes/proxy/caddy/Caddyfile index f1d1c48053f..040168117e8 100644 --- a/docker/volumes/proxy/caddy/Caddyfile +++ b/docker/volumes/proxy/caddy/Caddyfile @@ -2,7 +2,7 @@ @supabase_api path /auth/v1/* /rest/v1/* /graphql/v1 /realtime/v1/* /storage/v1/* /functions/v1/* /mcp /sso/* handle @supabase_api { - reverse_proxy kong:8000 + reverse_proxy api-gw:8000 } handle { diff --git a/docker/volumes/proxy/nginx/supabase-nginx.conf.tpl b/docker/volumes/proxy/nginx/supabase-nginx.conf.tpl index 24979499cf7..5a5c4f113d7 100644 --- a/docker/volumes/proxy/nginx/supabase-nginx.conf.tpl +++ b/docker/volumes/proxy/nginx/supabase-nginx.conf.tpl @@ -1,5 +1,5 @@ -upstream kong_upstream { - server kong:8000; +upstream api_gw_upstream { + server api-gw:8000; keepalive 2; } @@ -43,19 +43,19 @@ server { } location /auth { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } location /rest { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } location /graphql { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } location /realtime/v1/ { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -71,7 +71,7 @@ server { } location /storage/v1/ { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; proxy_buffering off; proxy_request_buffering off; @@ -82,14 +82,14 @@ server { } location /functions { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } location /mcp { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } location /sso { - proxy_pass http://kong_upstream; + proxy_pass http://api_gw_upstream; } }