Merge remote-tracking branch 'origin/next' into 8172-investigate-user-delete-bug

This commit is contained in:
Andras Bacsai 2026-04-06 18:30:04 +02:00
commit cb62b0d543
32 changed files with 501 additions and 223 deletions

View file

@ -9,9 +9,6 @@ body:
> [!IMPORTANT] > [!IMPORTANT]
> **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.) > **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.)
# 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
- If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new).
- type: textarea - type: textarea
attributes: attributes:
label: Error Message and Logs label: Error Message and Logs

View file

@ -1,31 +0,0 @@
name: 💎 Enhancement Bounty
description: "Propose a new feature, service, or improvement with an attached bounty."
title: "[Enhancement]: "
labels: ["✨ Enhancement", "🔍 Triage"]
body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
- type: dropdown
attributes:
label: Request Type
description: Select the type of request you are making.
options:
- New Feature
- New Service
- Improvement
validations:
required: true
- type: textarea
attributes:
label: Description
description: Provide a detailed description of the feature, improvement, or service you are proposing.
validations:
required: true

View file

@ -22,7 +22,7 @@ ## Category
## Preview ## Preview
<!-- Screenshot or short video showing your changes in action. Mandatory for bounty claims and new features. --> <!-- Screenshot or short video showing your changes in action. Mandatory for new features. -->
## AI Assistance ## AI Assistance

View file

@ -212,7 +212,7 @@ #### Review Process
- Duplicate or superseded work - Duplicate or superseded work
- Security or quality concerns - Security or quality concerns
#### Code Quality, Testing, and Bounty Submissions #### Code Quality and Testing
All contributions must adhere to the highest standards of code quality and testing: All contributions must adhere to the highest standards of code quality and testing:
- **Testing Required**: Every PR must include steps to test your changes. Untested code will not be reviewed or merged. - **Testing Required**: Every PR must include steps to test your changes. Untested code will not be reviewed or merged.
@ -220,15 +220,6 @@ #### Code Quality, Testing, and Bounty Submissions
- **Code Standards**: Follow the existing code style, conventions, and patterns in the codebase. - **Code Standards**: Follow the existing code style, conventions, and patterns in the codebase.
- **No AI-Generated Code**: Do not submit code generated by AI tools without fully understanding and verifying it. AI-generated submissions that are untested or incorrect will be rejected immediately. - **No AI-Generated Code**: Do not submit code generated by AI tools without fully understanding and verifying it. AI-generated submissions that are untested or incorrect will be rejected immediately.
**For PRs that claim bounties:**
- **Eligibility**: Bounty PRs must strictly follow all guidelines above. Untested, poorly described, or non-compliant PRs will not qualify for bounty rewards.
- **Original Work**: Bounties are for genuine contributions. Submitting AI-generated or copied code solely for bounty claims will result in disqualification and potential removal from contributing.
- **Quality Standards**: Bounty submissions are held to even higher standards. Ensure comprehensive testing, clear documentation, and alignment with project goals. When maintainers review the changes, they should work as expected (the things mentioned in the PR description plus what the bounty issuer needs).
- **Claim Process**: Only successfully merged PRs that pass all reviews (core maintainers + bounty issuer) and meet bounty criteria will be awarded. Follow the issue's bounty guidelines precisely.
- **Prioritization**: Contributor PRs are prioritized over first-time or new contributors.
- **Developer Experience**: We highly advise beginners to avoid participating in bug bounties for our codebase. Most of the time, they don't know what they are changing, how it affects other parts of the system, or if their changes are even correct.
- **Review Comments**: When maintainers ask questions, you should be able to respond properly without generic or AI-generated fluff.
## Development Notes ## Development Notes

View file

@ -4,7 +4,7 @@ # Coolify
An open-source & self-hostable Heroku / Netlify / Vercel alternative. An open-source & self-hostable Heroku / Netlify / Vercel alternative.
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest%20released%20version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge ![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest%20released%20version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge
) [![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new) )
</div> </div>
## About the Project ## About the Project
@ -65,7 +65,6 @@ ### Huge Sponsors
### Big Sponsors ### Big Sponsors
* [23M](https://23m.com?ref=coolify.io) - Your experts for high-availability hosting solutions! * [23M](https://23m.com?ref=coolify.io) - Your experts for high-availability hosting solutions!
* [Algora](https://algora.io?ref=coolify.io) - Open source contribution platform
* [American Cloud](https://americancloud.com?ref=coolify.io) - US-based cloud infrastructure services * [American Cloud](https://americancloud.com?ref=coolify.io) - US-based cloud infrastructure services
* [Arcjet](https://arcjet.com?ref=coolify.io) - Advanced web security and performance solutions * [Arcjet](https://arcjet.com?ref=coolify.io) - Advanced web security and performance solutions
* [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner * [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner

View file

@ -3,8 +3,8 @@
return [ return [
'coolify' => [ 'coolify' => [
'version' => '4.0.0-beta.471', 'version' => '4.0.0-beta.471',
'helper_version' => '1.0.12', 'helper_version' => '1.0.13',
'realtime_version' => '1.0.11', 'realtime_version' => '1.0.12',
'self_hosted' => env('SELF_HOSTED', true), 'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'), 'autoupdate' => env('AUTOUPDATE'),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),

View file

@ -60,7 +60,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.11' image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12'
ports: ports:
- "${SOKETI_PORT:-6001}:6001" - "${SOKETI_PORT:-6001}:6001"
- "6002:6002" - "6002:6002"

View file

@ -96,7 +96,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.10' image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12'
pull_policy: always pull_policy: always
container_name: coolify-realtime container_name: coolify-realtime
restart: always restart: always

View file

@ -28,7 +28,8 @@ ARG NIXPACKS_VERSION
USER root USER root
WORKDIR /artifacts WORKDIR /artifacts
RUN apk add --no-cache bash curl git git-lfs openssh-client tar tini RUN apk upgrade --no-cache && \
apk add --no-cache bash curl git git-lfs openssh-client tar tini
RUN mkdir -p ~/.docker/cli-plugins RUN mkdir -p ~/.docker/cli-plugins
RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \ curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \

View file

@ -10,7 +10,8 @@ ARG TARGETPLATFORM
ARG CLOUDFLARED_VERSION ARG CLOUDFLARED_VERSION
WORKDIR /terminal WORKDIR /terminal
RUN apk add --no-cache openssh-client make g++ python3 curl RUN apk upgrade --no-cache && \
apk add --no-cache openssh-client make g++ python3 curl
COPY docker/coolify-realtime/package.json ./ COPY docker/coolify-realtime/package.json ./
RUN npm i RUN npm i
RUN npm rebuild node-pty --update-binary RUN npm rebuild node-pty --update-binary

View file

@ -33,7 +33,8 @@ RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \
docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx
# Install PostgreSQL repository and keys # Install PostgreSQL repository and keys
RUN apk add --no-cache gnupg && \ RUN apk upgrade --no-cache && \
apk add --no-cache gnupg && \
mkdir -p /usr/share/keyrings && \ mkdir -p /usr/share/keyrings && \
curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg

View file

@ -60,7 +60,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.11' image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12'
ports: ports:
- "${SOKETI_PORT:-6001}:6001" - "${SOKETI_PORT:-6001}:6001"
- "6002:6002" - "6002:6002"

View file

@ -96,7 +96,7 @@ services:
retries: 10 retries: 10
timeout: 2s timeout: 2s
soketi: soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.10' image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12'
pull_policy: always pull_policy: always
container_name: coolify-realtime container_name: coolify-realtime
restart: always restart: always

View file

@ -7,10 +7,10 @@
"version": "4.0.0" "version": "4.0.0"
}, },
"helper": { "helper": {
"version": "1.0.12" "version": "1.0.13"
}, },
"realtime": { "realtime": {
"version": "1.0.11" "version": "1.0.12"
}, },
"sentinel": { "sentinel": {
"version": "0.0.21" "version": "0.0.21"

4
public/svgs/grimmory.svg Normal file
View file

@ -0,0 +1,4 @@
<svg width="126" height="126" viewBox="0 0 126 126" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M59 4.79297C71.5051 11.5557 80 24.7854 80 40C80 40.5959 79.987 41.1888 79.9609 41.7783C79.8609 44.0406 81.7355 46 84 46C106.091 46 124 63.9086 124 86C124 108.091 106.091 126 84 126H10C4.47715 126 0 121.523 0 116V39.0068L0.0126953 38.9941C0.357624 25.0252 7.86506 12.8347 19 5.95215V63.832C19 64.8345 20.0676 65.4391 20.9121 64.9902L21.0771 64.8867L38.2227 52.3428C38.6819 52.0068 39.3064 52.0068 39.7656 52.3428L56.9229 64.8945L57.0879 64.998C57.9324 65.447 59 64.8423 59 63.8398V4.79297Z" fill="#818cf8"/>
<path d="M40 0C43.8745 0 47.6199 0.552381 51.1631 1.58008V50.9697L44.3926 46.0176L44.0879 45.8037C40.9061 43.6679 36.7098 43.7393 33.5957 46.0176L26.8369 50.9619V2.21875C30.9593 0.782634 35.3881 0 40 0Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 842 B

View file

@ -7,7 +7,7 @@
services: services:
frontend: frontend:
image: ghcr.io/smaug6739/alexandrie-frontend:v8.4.1 image: ghcr.io/smaug6739/alexandrie-frontend:v8.7.2
environment: environment:
- SERVICE_URL_FRONTEND_8200 - SERVICE_URL_FRONTEND_8200
- PORT=8200 - PORT=8200
@ -21,7 +21,7 @@ services:
- backend - backend
backend: backend:
image: ghcr.io/smaug6739/alexandrie-backend:v8.4.1 image: ghcr.io/smaug6739/alexandrie-backend:v8.7.2
environment: environment:
- SERVICE_URL_BACKEND_8201 - SERVICE_URL_BACKEND_8201
- BACKEND_PORT=8201 - BACKEND_PORT=8201
@ -74,7 +74,7 @@ services:
retries: 5 retries: 5
rustfs: rustfs:
image: rustfs/rustfs:1.0.0-alpha.81 image: rustfs/rustfs:1.0.0-alpha.90
environment: environment:
- SERVICE_URL_RUSTFS_9000 - SERVICE_URL_RUSTFS_9000
- RUSTFS_ACCESS_KEY=${SERVICE_USER_RUSTFS} - RUSTFS_ACCESS_KEY=${SERVICE_USER_RUSTFS}

View file

@ -7,7 +7,7 @@
services: services:
backend: backend:
image: ghcr.io/get-convex/convex-backend:00bd92723422f3bff968230c94ccdeb8c1719832 image: ghcr.io/get-convex/convex-backend:a9a760ca10399ed42e1b4bb87c78539a235488c7
volumes: volumes:
- data:/convex/data - data:/convex/data
environment: environment:
@ -47,7 +47,7 @@ services:
start_period: 10s start_period: 10s
dashboard: dashboard:
image: ghcr.io/get-convex/convex-dashboard:33cef775a8a6228cbacee4a09ac2c4073d62ed13 image: ghcr.io/get-convex/convex-dashboard:a9a760ca10399ed42e1b4bb87c78539a235488c7
environment: environment:
- SERVICE_URL_DASHBOARD_6791 - SERVICE_URL_DASHBOARD_6791
# URL of the Convex API as accessed by the dashboard (browser). # URL of the Convex API as accessed by the dashboard (browser).
@ -56,6 +56,6 @@ services:
backend: backend:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: wget -qO- http://127.0.0.1:6791/ test: curl -f http://127.0.0.1:6791/
interval: 5s interval: 5s
start_period: 5s start_period: 5s

View file

@ -27,6 +27,11 @@ services:
- REDIS_HOST=redis - REDIS_HOST=redis
- REDIS_PORT=6379 - REDIS_PORT=6379
- WEBSOCKETS_ENABLED=true - WEBSOCKETS_ENABLED=true
- CORS_ENABLED=${CORS_ENABLED:-true}
- CORS_ORIGIN=${CORS_ORIGIN}
- CORS_METHODS=${CORS_METHODS:-GET,POST,PATCH,DELETE,OPTIONS}
- CORS_ALLOWED_HEADERS=${CORS_ALLOWED_HEADERS:-Content-Type,Authorization}
- CORS_CREDENTIALS=${CORS_CREDENTIALS:-true}
healthcheck: healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"] test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"]
interval: 5s interval: 5s

View file

@ -22,6 +22,11 @@ services:
- DB_CLIENT=sqlite3 - DB_CLIENT=sqlite3
- DB_FILENAME=/directus/database/data.db - DB_FILENAME=/directus/database/data.db
- WEBSOCKETS_ENABLED=true - WEBSOCKETS_ENABLED=true
- CORS_ENABLED=${CORS_ENABLED:-true}
- CORS_ORIGIN=${CORS_ORIGIN}
- CORS_METHODS=${CORS_METHODS:-GET,POST,PATCH,DELETE,OPTIONS}
- CORS_ALLOWED_HEADERS=${CORS_ALLOWED_HEADERS:-Content-Type,Authorization}
- CORS_CREDENTIALS=${CORS_CREDENTIALS:-true}
healthcheck: healthcheck:
test: test:
["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"] ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8055/admin/login"]

View file

@ -0,0 +1,49 @@
# documentation: https://github.com/grimmory-tools/grimmory
# slogan: Grimmory is a self-hosted application for managing your entire book collection in one place. Organize, read, annotate, sync across devices, and share without relying on third-party services.
# tags: books,ebooks,library,reader
# logo: svgs/grimmory.svg
# port: 80
services:
grimmory:
image: 'grimmory/grimmory:nightly-20260403-3a371f7' # Released on April 3 2026
environment:
- SERVICE_URL_GRIMMORY_80
- 'USER_ID=${GRIMMORY_USER_ID:-0}'
- 'GROUP_ID=${GRIMMORY_GROUP_ID:-0}'
- 'TZ=${TZ:-UTC}'
- 'DATABASE_URL=jdbc:mariadb://mariadb:3306/${MARIADB_DATABASE:-grimmory-db}'
- 'DATABASE_USERNAME=${SERVICE_USER_MARIADB}'
- 'DATABASE_PASSWORD=${SERVICE_PASSWORD_MARIADB}'
- BOOKLORE_PORT=80
volumes:
- 'grimmory-data:/app/data'
- 'grimmory-books:/books'
- 'grimmory-bookdrop:/bookdrop'
healthcheck:
test: 'wget --no-verbose --tries=1 --spider http://127.0.0.1/health || exit 1'
interval: 10s
timeout: 5s
retries: 10
depends_on:
mariadb:
condition: service_healthy
mariadb:
image: 'mariadb:12'
environment:
- 'MARIADB_USER=${SERVICE_USER_MARIADB}'
- 'MARIADB_PASSWORD=${SERVICE_PASSWORD_MARIADB}'
- 'MARIADB_ROOT_PASSWORD=${SERVICE_PASSWORD_MARIADBROOT}'
- 'MARIADB_DATABASE=${MARIADB_DATABASE:-grimmory-db}'
volumes:
- 'mariadb-data:/var/lib/mysql'
healthcheck:
test:
- CMD
- healthcheck.sh
- '--connect'
- '--innodb_initialized'
interval: 10s
timeout: 5s
retries: 10

View file

@ -7,7 +7,7 @@
services: services:
librechat: librechat:
image: ghcr.io/danny-avila/librechat-dev-api:latest image: ghcr.io/danny-avila/librechat-dev-api:6ecd1b510faaa593ad954fb6276c18e5f12a8e53 # Released on April 2
environment: environment:
- SERVICE_URL_LIBRECHAT_3080 - SERVICE_URL_LIBRECHAT_3080
- DOMAIN_CLIENT=${SERVICE_URL_LIBRECHAT} - DOMAIN_CLIENT=${SERVICE_URL_LIBRECHAT}
@ -64,7 +64,7 @@ services:
"--no-verbose", "--no-verbose",
"--tries=1", "--tries=1",
"--spider", "--spider",
"http://127.0.0.1:3080/api/health", "http://127.0.0.1:3080/health",
] ]
interval: 5s interval: 5s
timeout: 10s timeout: 10s
@ -92,7 +92,7 @@ services:
retries: 3 retries: 3
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.12.3 image: getmeili/meilisearch:v1.35.1
environment: environment:
- MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI} - MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILI}
- MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-false} - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-false}
@ -107,7 +107,7 @@ services:
retries: 15 retries: 15
vectordb: vectordb:
image: ankane/pgvector:latest image: ankane/pgvector:v0.5.1 # pgvector by ankane is archived and not maintained, in future we have to swap this image to something else that is well maintained
environment: environment:
- POSTGRES_DB=rag - POSTGRES_DB=rag
- POSTGRES_USER=${SERVICE_USER_POSTGRES} - POSTGRES_USER=${SERVICE_USER_POSTGRES}
@ -129,7 +129,7 @@ services:
start_period: 10s start_period: 10s
rag-api: rag-api:
image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:v0.7.3
environment: environment:
- POSTGRES_DB=rag - POSTGRES_DB=rag
- POSTGRES_USER=${SERVICE_USER_POSTGRES} - POSTGRES_USER=${SERVICE_USER_POSTGRES}

View file

@ -1,6 +1,6 @@
# documentation: https://github.com/itzg/docker-minecraft-server # documentation: https://github.com/itzg/docker-minecraft-server
# slogan: Minecraft Server that will automatically download selected version at startup. # slogan: Minecraft Server that will automatically download selected version at startup.
# category: media # category: games
# tags: minecraft # tags: minecraft
# logo: svgs/minecraft.svg # logo: svgs/minecraft.svg
# port: 25565 # port: 25565

View file

@ -48,7 +48,7 @@ services:
redis: redis:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/"] test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz"]
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10
@ -133,7 +133,7 @@ services:
healthcheck: healthcheck:
test: test:
- CMD-SHELL - CMD-SHELL
- 'wget -qO- http://127.0.0.1:5680/' - 'wget -qO- http://127.0.0.1:5680/healthz'
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10

View file

@ -41,7 +41,7 @@ services:
postgresql: postgresql:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/"] test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz"]
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10
@ -58,7 +58,7 @@ services:
healthcheck: healthcheck:
test: test:
- CMD-SHELL - CMD-SHELL
- 'wget -qO- http://127.0.0.1:5680/' - 'wget -qO- http://127.0.0.1:5680/healthz'
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10

View file

@ -32,7 +32,7 @@ services:
volumes: volumes:
- n8n-data:/home/node/.n8n - n8n-data:/home/node/.n8n
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/"] test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:5678/healthz"]
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10
@ -49,7 +49,7 @@ services:
healthcheck: healthcheck:
test: test:
- CMD-SHELL - CMD-SHELL
- 'wget -qO- http://127.0.0.1:5680/' - 'wget -qO- http://127.0.0.1:5680/healthz'
interval: 5s interval: 5s
timeout: 20s timeout: 20s
retries: 10 retries: 10

View file

@ -29,7 +29,7 @@ services:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"] test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
interval: 2s interval: 30s
timeout: 10s timeout: 10s
retries: 15 retries: 15

View file

@ -29,7 +29,7 @@ services:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"] test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
interval: 2s interval: 30s
timeout: 10s timeout: 10s
retries: 15 retries: 15

View file

@ -29,7 +29,7 @@ services:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"] test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
interval: 2s interval: 30s
timeout: 10s timeout: 10s
retries: 15 retries: 15

View file

@ -18,6 +18,6 @@ services:
- nextcloud-data:/data - nextcloud-data:/data
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"] test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
interval: 2s interval: 30s
timeout: 10s timeout: 10s
retries: 15 retries: 15

View file

@ -7,14 +7,13 @@
services: services:
rivet-engine: rivet-engine:
image: rivetkit/engine:25.8.0 image: rivetdev/engine:2.2.0
environment: environment:
- SERVICE_URL_RIVET_6420 - SERVICE_URL_RIVET_6420
- 'RIVET__AUTH__ADMIN_TOKEN=${SERVICE_PASSWORD_RIVET}' - RIVET__FILE_SYSTEM__PATH=/data
- RIVET__POSTGRES__URL=postgresql://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgresql:5432/${POSTGRESQL_DATABASE-rivet} - 'RIVET__AUTH__ADMIN_TOKEN=${SERVICE_BASE64_TOKEN}'
depends_on: volumes:
postgresql: - 'rivet-data:/data'
condition: service_healthy
healthcheck: healthcheck:
test: test:
- CMD - CMD
@ -24,19 +23,4 @@ services:
interval: 2s interval: 2s
timeout: 10s timeout: 10s
retries: 10 retries: 10
start_period: 30s start_period: 30s
postgresql:
image: postgres:17-alpine
volumes:
- rivet-postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- POSTGRES_DB=${POSTGRESQL_DATABASE-rivet}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10

View file

@ -8,33 +8,77 @@
services: services:
supabase-kong: supabase-kong:
image: kong:2.8.1 image: kong/kong:3.9.1
# https://unix.stackexchange.com/a/294837 entrypoint: /home/kong/kong-entrypoint.sh
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
depends_on: depends_on:
supabase-analytics: supabase-analytics:
condition: service_healthy condition: service_healthy
healthcheck:
test: ["CMD", "kong", "health"]
interval: 5s
timeout: 5s
retries: 5
environment: environment:
- SERVICE_URL_SUPABASEKONG_8000 - SERVICE_URL_SUPABASEKONG_8000
- KONG_PORT_MAPS=443:8000 - KONG_PORT_MAPS=443:8000
- JWT_SECRET=${SERVICE_PASSWORD_JWT} - JWT_SECRET=${SERVICE_PASSWORD_JWT}
- KONG_DATABASE=off - KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml - KONG_DECLARATIVE_CONFIG=/usr/local/kong/kong.yml
# https://github.com/supabase/cli/issues/14 # https://github.com/supabase/cli/issues/14
- KONG_DNS_ORDER=LAST,A,CNAME - KONG_DNS_ORDER=LAST,A,CNAME
- KONG_PLUGINS=request-transformer,cors,key-auth,acl,basic-auth - KONG_DNS_NOT_FOUND_TTL=1
- KONG_PLUGINS=request-transformer,cors,key-auth,acl,basic-auth,request-termination,ip-restriction,post-function
- KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k - KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k
- KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k - KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k
- 'KONG_PROXY_ACCESS_LOG=/dev/stdout combined'
- SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY} - SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
- SUPABASE_SERVICE_KEY=${SERVICE_SUPABASESERVICE_KEY} - SUPABASE_SERVICE_KEY=${SERVICE_SUPABASESERVICE_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=${SERVICE_USER_ADMIN} - DASHBOARD_USERNAME=${SERVICE_USER_ADMIN}
- DASHBOARD_PASSWORD=${SERVICE_PASSWORD_ADMIN} - DASHBOARD_PASSWORD=${SERVICE_PASSWORD_ADMIN}
- 'KONG_STORAGE_CONNECT_TIMEOUT=${KONG_STORAGE_CONNECT_TIMEOUT:-60}' - 'KONG_STORAGE_CONNECT_TIMEOUT=${KONG_STORAGE_CONNECT_TIMEOUT:-60}'
- 'KONG_STORAGE_WRITE_TIMEOUT=${KONG_STORAGE_WRITE_TIMEOUT:-3600}' - 'KONG_STORAGE_WRITE_TIMEOUT=${KONG_STORAGE_WRITE_TIMEOUT:-3600}'
- 'KONG_STORAGE_READ_TIMEOUT=${KONG_STORAGE_READ_TIMEOUT:-3600}' - 'KONG_STORAGE_READ_TIMEOUT=${KONG_STORAGE_READ_TIMEOUT:-3600}'
- 'KONG_STORAGE_REQUEST_BUFFERING=${KONG_STORAGE_REQUEST_BUFFERING:-false}' - 'KONG_STORAGE_REQUEST_BUFFERING=${KONG_STORAGE_REQUEST_BUFFERING:-false}'
- 'KONG_STORAGE_RESPONSE_BUFFERING=${KONG_STORAGE_RESPONSE_BUFFERING:-false}' - 'KONG_STORAGE_RESPONSE_BUFFERING=${KONG_STORAGE_RESPONSE_BUFFERING:-false}'
volumes: volumes:
- type: bind
source: ./volumes/api/kong-entrypoint.sh
target: /home/kong/kong-entrypoint.sh
content: |
#!/bin/bash
# Custom entrypoint for Kong that builds Lua expressions for request-transformer
# and performs environment variable substitution in the declarative config.
if [ -n "$SUPABASE_SECRET_KEY" ] && [ -n "$SUPABASE_PUBLISHABLE_KEY" ]; then
export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '$SUPABASE_SECRET_KEY' and 'Bearer $SERVICE_ROLE_KEY_ASYMMETRIC') or (headers.apikey == '$SUPABASE_PUBLISHABLE_KEY' and 'Bearer $ANON_KEY_ASYMMETRIC') or headers.apikey)"
export LUA_RT_WS_EXPR="\$((query_params.apikey == '$SUPABASE_SECRET_KEY' and '$SERVICE_ROLE_KEY_ASYMMETRIC') or (query_params.apikey == '$SUPABASE_PUBLISHABLE_KEY' and '$ANON_KEY_ASYMMETRIC') or query_params.apikey)"
else
export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or headers.apikey)"
export LUA_RT_WS_EXPR="\$(query_params.apikey)"
fi
awk '{
result = ""
rest = $0
while (match(rest, /\$[A-Za-z_][A-Za-z_0-9]*/)) {
varname = substr(rest, RSTART + 1, RLENGTH - 1)
if (varname in ENVIRON) {
result = result substr(rest, 1, RSTART - 1) ENVIRON[varname]
} else {
result = result substr(rest, 1, RSTART + RLENGTH - 1)
}
rest = substr(rest, RSTART + RLENGTH)
}
print result rest
}' /home/kong/temp.yml > "$KONG_DECLARATIVE_CONFIG"
sed -i '/^[[:space:]]*- key:[[:space:]]*$/d' "$KONG_DECLARATIVE_CONFIG"
exec /entrypoint.sh kong docker-start
# https://github.com/supabase/supabase/issues/12661 # https://github.com/supabase/supabase/issues/12661
- type: bind - type: bind
source: ./volumes/api/kong.yml source: ./volumes/api/kong.yml
@ -51,9 +95,11 @@ services:
- username: anon - username: anon
keyauth_credentials: keyauth_credentials:
- key: $SUPABASE_ANON_KEY - key: $SUPABASE_ANON_KEY
- key: $SUPABASE_PUBLISHABLE_KEY
- username: service_role - username: service_role
keyauth_credentials: keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY - key: $SUPABASE_SERVICE_KEY
- key: $SUPABASE_SECRET_KEY
### ###
### Access Control List ### Access Control List
@ -69,8 +115,8 @@ services:
### ###
basicauth_credentials: basicauth_credentials:
- consumer: DASHBOARD - consumer: DASHBOARD
username: $DASHBOARD_USERNAME username: '$DASHBOARD_USERNAME'
password: $DASHBOARD_PASSWORD password: '$DASHBOARD_PASSWORD'
### ###
@ -106,6 +152,36 @@ services:
- /auth/v1/authorize - /auth/v1/authorize
plugins: plugins:
- name: cors - name: cors
- name: auth-v1-open-jwks
_comment: 'Auth: /auth/v1/.well-known/jwks.json -> http://supabase-auth:9999/.well-known/jwks.json'
url: http://supabase-auth:9999/.well-known/jwks.json
routes:
- name: auth-v1-open-jwks
strip_path: true
paths:
- /auth/v1/.well-known/jwks.json
plugins:
- name: cors
- name: auth-v1-open-sso-acs
url: "http://supabase-auth:9999/sso/saml/acs"
routes:
- name: auth-v1-open-sso-acs
strip_path: true
paths:
- /sso/saml/acs
plugins:
- name: cors
- name: auth-v1-open-sso-metadata
url: "http://supabase-auth:9999/sso/saml/metadata"
routes:
- name: auth-v1-open-sso-metadata
strip_path: true
paths:
- /sso/saml/metadata
plugins:
- name: cors
## Secure Auth routes ## Secure Auth routes
- name: auth-v1 - name: auth-v1
@ -121,6 +197,14 @@ services:
- name: key-auth - name: key-auth
config: config:
hide_credentials: false hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl - name: acl
config: config:
hide_groups_header: true hide_groups_header: true
@ -141,7 +225,15 @@ services:
- name: cors - name: cors
- name: key-auth - name: key-auth
config: config:
hide_credentials: true hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl - name: acl
config: config:
hide_groups_header: true hide_groups_header: true
@ -162,12 +254,17 @@ services:
- name: cors - name: cors
- name: key-auth - name: key-auth
config: config:
hide_credentials: true hide_credentials: false
- name: request-transformer - name: request-transformer
config: config:
add: add:
headers: headers:
- Content-Profile:graphql_public - "Content-Profile: graphql_public"
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Content-Profile: graphql_public"
- "Authorization: $LUA_AUTH_EXPR"
- name: acl - name: acl
config: config:
hide_groups_header: true hide_groups_header: true
@ -190,6 +287,14 @@ services:
- name: key-auth - name: key-auth
config: config:
hide_credentials: false hide_credentials: false
- name: request-transformer
config:
add:
querystring:
- "apikey: $LUA_RT_WS_EXPR"
replace:
querystring:
- "apikey: $LUA_RT_WS_EXPR"
- name: acl - name: acl
config: config:
hide_groups_header: true hide_groups_header: true
@ -197,7 +302,7 @@ services:
- admin - admin
- anon - anon
- name: realtime-v1-rest - name: realtime-v1-rest
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*' _comment: 'Realtime: /realtime/v1/api/* -> http://realtime:4000/api/*'
url: http://realtime-dev:4000/api url: http://realtime-dev:4000/api
protocol: http protocol: http
routes: routes:
@ -210,6 +315,14 @@ services:
- name: key-auth - name: key-auth
config: config:
hide_credentials: false hide_credentials: false
- name: request-transformer
config:
add:
headers:
- "Authorization: $LUA_AUTH_EXPR"
replace:
headers:
- "Authorization: $LUA_AUTH_EXPR"
- name: acl - name: acl
config: config:
hide_groups_header: true hide_groups_header: true
@ -217,7 +330,8 @@ services:
- admin - admin
- anon - anon
## Storage routes: the storage server manages its own auth ## Storage API endpoint
## No key-auth - S3 protocol requests don't carry an apikey header.
- name: storage-v1 - name: storage-v1
_comment: 'Storage: /storage/v1/* -> http://supabase-storage:5000/*' _comment: 'Storage: /storage/v1/* -> http://supabase-storage:5000/*'
connect_timeout: $KONG_STORAGE_CONNECT_TIMEOUT connect_timeout: $KONG_STORAGE_CONNECT_TIMEOUT
@ -233,11 +347,20 @@ services:
response_buffering: $KONG_STORAGE_RESPONSE_BUFFERING response_buffering: $KONG_STORAGE_RESPONSE_BUFFERING
plugins: plugins:
- name: cors - name: cors
- name: post-function
config:
access:
- |
local auth = kong.request.get_header("authorization")
if auth == nil or auth == "" or auth:find("^%s*$") then
kong.service.request.clear_header("authorization")
end
## Edge Functions routes ## Edge Functions routes
- name: functions-v1 - name: functions-v1
_comment: 'Edge Functions: /functions/v1/* -> http://supabase-edge-functions:9000/*' _comment: 'Edge Functions: /functions/v1/* -> http://supabase-edge-functions:9000/*'
url: http://supabase-edge-functions:9000/ url: http://supabase-edge-functions:9000/
read_timeout: 150000
routes: routes:
- name: functions-v1-all - name: functions-v1-all
strip_path: true strip_path: true
@ -246,15 +369,28 @@ services:
plugins: plugins:
- name: cors - name: cors
## Analytics routes ## OAuth 2.0 Authorization Server Metadata (RFC 8414)
- name: analytics-v1 - name: well-known-oauth
_comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*' _comment: 'Auth: /.well-known/oauth-authorization-server -> http://supabase-auth:9999/.well-known/oauth-authorization-server'
url: http://supabase-analytics:4000/ url: http://supabase-auth:9999/.well-known/oauth-authorization-server
routes: routes:
- name: analytics-v1-all - name: well-known-oauth
strip_path: true strip_path: true
paths: paths:
- /analytics/v1/ - /.well-known/oauth-authorization-server
plugins:
- name: cors
## Analytics routes
## Not used - Studio and Vector talk directly to analytics via Docker networking.
# - name: analytics-v1
# _comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
# url: http://supabase-analytics:4000/
# routes:
# - name: analytics-v1-all
# strip_path: true
# paths:
# - /analytics/v1/
## Secure Database routes ## Secure Database routes
- name: meta - name: meta
@ -275,6 +411,48 @@ services:
allow: allow:
- admin - admin
## Block access to /api/mcp
- name: mcp-blocker
_comment: 'Block direct access to /api/mcp'
url: http://supabase-studio:3000/api/mcp
routes:
- name: mcp-blocker-route
strip_path: true
paths:
- /api/mcp
plugins:
- name: request-termination
config:
status_code: 403
message: "Access is forbidden."
## MCP endpoint - local access
- name: mcp
_comment: 'MCP: /mcp -> http://supabase-studio:3000/api/mcp (local access)'
url: http://supabase-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
# deny: []
## Protected Dashboard - catch all remaining routes ## Protected Dashboard - catch all remaining routes
- name: dashboard - name: dashboard
_comment: 'Studio: /* -> http://studio:3000/*' _comment: 'Studio: /* -> http://studio:3000/*'
@ -290,7 +468,7 @@ services:
config: config:
hide_credentials: true hide_credentials: true
supabase-studio: supabase-studio:
image: supabase/studio:2026.01.07-sha-037e5f9 image: supabase/studio:2026.03.16-sha-5528817
healthcheck: healthcheck:
test: test:
[ [
@ -310,7 +488,11 @@ services:
- STUDIO_PG_META_URL=http://supabase-meta:8080 - STUDIO_PG_META_URL=http://supabase-meta:8080
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_HOST=${POSTGRES_HOST:-supabase-db} - POSTGRES_HOST=${POSTGRES_HOST:-supabase-db}
- CURRENT_CLI_VERSION=2.67.1 - POSTGRES_PORT=${POSTGRES_PORT:-5432}
- POSTGRES_DB=${POSTGRES_DB:-postgres}
- 'PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public,storage,graphql_public}'
- PGRST_DB_MAX_ROWS=${PGRST_DB_MAX_ROWS:-1000}
- PGRST_DB_EXTRA_SEARCH_PATH=${PGRST_DB_EXTRA_SEARCH_PATH:-public}
- DEFAULT_ORGANIZATION_NAME=${STUDIO_DEFAULT_ORGANIZATION:-Default Organization} - DEFAULT_ORGANIZATION_NAME=${STUDIO_DEFAULT_ORGANIZATION:-Default Organization}
- DEFAULT_PROJECT_NAME=${STUDIO_DEFAULT_PROJECT:-Default Project} - DEFAULT_PROJECT_NAME=${STUDIO_DEFAULT_PROJECT:-Default Project}
@ -320,10 +502,12 @@ services:
- SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY} - SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
- SUPABASE_SERVICE_KEY=${SERVICE_SUPABASESERVICE_KEY} - SUPABASE_SERVICE_KEY=${SERVICE_SUPABASESERVICE_KEY}
- AUTH_JWT_SECRET=${SERVICE_PASSWORD_JWT} - AUTH_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- PG_META_CRYPTO_KEY=${SERVICE_PASSWORD_PGMETACRYPTO}
- LOGFLARE_API_KEY=${SERVICE_PASSWORD_LOGFLARE} - LOGFLARE_API_KEY=${SERVICE_PASSWORD_LOGFLARE}
- LOGFLARE_PUBLIC_ACCESS_TOKEN=${SERVICE_PASSWORD_LOGFLARE}
- LOGFLARE_PRIVATE_ACCESS_TOKEN=${SERVICE_PASSWORD_LOGFLAREPRIVATE}
- LOGFLARE_URL=http://supabase-analytics:4000 - LOGFLARE_URL=http://supabase-analytics:4000
- 'SUPABASE_PUBLIC_API=${SERVICE_URL_SUPABASEKONG}'
# Next.js client-side environment variables (required for browser access) # Next.js client-side environment variables (required for browser access)
- 'NEXT_PUBLIC_SUPABASE_URL=${SERVICE_URL_SUPABASEKONG}' - 'NEXT_PUBLIC_SUPABASE_URL=${SERVICE_URL_SUPABASEKONG}'
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY} - NEXT_PUBLIC_SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
@ -333,8 +517,13 @@ services:
# Uncomment to use Big Query backend for analytics # Uncomment to use Big Query backend for analytics
# NEXT_ANALYTICS_BACKEND_PROVIDER=bigquery # NEXT_ANALYTICS_BACKEND_PROVIDER=bigquery
- 'OPENAI_API_KEY=${OPENAI_API_KEY}' - 'OPENAI_API_KEY=${OPENAI_API_KEY}'
- SNIPPETS_MANAGEMENT_FOLDER=/app/snippets
- EDGE_FUNCTIONS_MANAGEMENT_FOLDER=/app/edge-functions
volumes:
- ./volumes/snippets:/app/snippets
- ./volumes/functions:/app/edge-functions
supabase-db: supabase-db:
image: supabase/postgres:15.8.1.048 image: supabase/postgres:15.8.1.085
healthcheck: healthcheck:
test: pg_isready -U postgres -h 127.0.0.1 test: pg_isready -U postgres -h 127.0.0.1
interval: 5s interval: 5s
@ -365,7 +554,7 @@ services:
source: ./volumes/db/realtime.sql source: ./volumes/db/realtime.sql
target: /docker-entrypoint-initdb.d/migrations/99-realtime.sql target: /docker-entrypoint-initdb.d/migrations/99-realtime.sql
content: | content: |
\set pguser `echo "supabase_admin"` \set pguser `echo "$POSTGRES_USER"`
create schema if not exists _realtime; create schema if not exists _realtime;
alter schema _realtime owner to :pguser; alter schema _realtime owner to :pguser;
@ -380,7 +569,7 @@ services:
source: ./volumes/db/pooler.sql source: ./volumes/db/pooler.sql
target: /docker-entrypoint-initdb.d/migrations/99-pooler.sql target: /docker-entrypoint-initdb.d/migrations/99-pooler.sql
content: | content: |
\set pguser `echo "supabase_admin"` \set pguser `echo "$POSTGRES_USER"`
\c _supabase \c _supabase
create schema if not exists _supavisor; create schema if not exists _supavisor;
alter schema _supavisor owner to :pguser; alter schema _supavisor owner to :pguser;
@ -624,7 +813,7 @@ services:
source: ./volumes/db/logs.sql source: ./volumes/db/logs.sql
target: /docker-entrypoint-initdb.d/migrations/99-logs.sql target: /docker-entrypoint-initdb.d/migrations/99-logs.sql
content: | content: |
\set pguser `echo "supabase_admin"` \set pguser `echo "$POSTGRES_USER"`
\c _supabase \c _supabase
create schema if not exists _analytics; create schema if not exists _analytics;
alter schema _analytics owner to :pguser; alter schema _analytics owner to :pguser;
@ -633,7 +822,7 @@ services:
- supabase-db-config:/etc/postgresql-custom - supabase-db-config:/etc/postgresql-custom
supabase-analytics: supabase-analytics:
image: supabase/logflare:1.4.0 image: supabase/logflare:1.31.2
healthcheck: healthcheck:
test: ["CMD", "curl", "http://127.0.0.1:4000/health"] test: ["CMD", "curl", "http://127.0.0.1:4000/health"]
timeout: 5s timeout: 5s
@ -655,11 +844,10 @@ services:
- DB_PORT=${POSTGRES_PORT:-5432} - DB_PORT=${POSTGRES_PORT:-5432}
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DB_SCHEMA=_analytics - DB_SCHEMA=_analytics
- LOGFLARE_API_KEY=${SERVICE_PASSWORD_LOGFLARE} - LOGFLARE_PUBLIC_ACCESS_TOKEN=${SERVICE_PASSWORD_LOGFLARE}
- LOGFLARE_PRIVATE_ACCESS_TOKEN=${SERVICE_PASSWORD_LOGFLAREPRIVATE}
- LOGFLARE_SINGLE_TENANT=true - LOGFLARE_SINGLE_TENANT=true
- LOGFLARE_SINGLE_TENANT_MODE=true
- LOGFLARE_SUPABASE_MODE=true - LOGFLARE_SUPABASE_MODE=true
- LOGFLARE_MIN_CLUSTER_SIZE=1
# Comment variables to use Big Query backend for analytics # Comment variables to use Big Query backend for analytics
- POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/_supabase - POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/_supabase
@ -670,7 +858,7 @@ services:
# GOOGLE_PROJECT_ID=${GOOGLE_PROJECT_ID} # GOOGLE_PROJECT_ID=${GOOGLE_PROJECT_ID}
# GOOGLE_PROJECT_NUMBER=${GOOGLE_PROJECT_NUMBER} # GOOGLE_PROJECT_NUMBER=${GOOGLE_PROJECT_NUMBER}
supabase-vector: supabase-vector:
image: timberio/vector:0.28.1-alpine image: timberio/vector:0.53.0-alpine
healthcheck: healthcheck:
test: test:
[ [
@ -722,13 +910,13 @@ services:
inputs: inputs:
- project_logs - project_logs
route: route:
kong: 'starts_with(string!(.appname), "supabase-kong")' kong: 'contains(string!(.appname), "supabase-kong")'
auth: 'starts_with(string!(.appname), "supabase-auth")' auth: 'contains(string!(.appname), "supabase-auth")'
rest: 'starts_with(string!(.appname), "supabase-rest")' rest: 'contains(string!(.appname), "supabase-rest")'
realtime: 'starts_with(string!(.appname), "realtime-dev")' realtime: 'contains(string!(.appname), "supabase-realtime")'
storage: 'starts_with(string!(.appname), "supabase-storage")' storage: 'contains(string!(.appname), "supabase-storage")'
functions: 'starts_with(string!(.appname), "supabase-functions")' functions: 'contains(string!(.appname), "supabase-edge-functions")'
db: 'starts_with(string!(.appname), "supabase-db")' db: 'contains(string!(.appname), "supabase-db")'
# Ignores non nginx errors since they are related with kong booting up # Ignores non nginx errors since they are related with kong booting up
kong_logs: kong_logs:
type: remap type: remap
@ -741,10 +929,13 @@ services:
.metadata.request.headers.referer = req.referer .metadata.request.headers.referer = req.referer
.metadata.request.headers.user_agent = req.agent .metadata.request.headers.user_agent = req.agent
.metadata.request.headers.cf_connecting_ip = req.client .metadata.request.headers.cf_connecting_ip = req.client
.metadata.request.method = req.method
.metadata.request.path = req.path
.metadata.request.protocol = req.protocol
.metadata.response.status_code = req.status .metadata.response.status_code = req.status
url, split_err = split(req.request, " ")
if split_err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
} }
if err != null { if err != null {
abort abort
@ -793,14 +984,20 @@ services:
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$') parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
if err == null { if err == null {
.event_message = parsed.msg .event_message = parsed.msg
.timestamp = to_timestamp!(parsed.time) .timestamp = parse_timestamp!(value: parsed.time, format: "%d/%b/%Y:%H:%M:%S %z")
.metadata.host = .project .metadata.host = .project
} }
# Filter out healthcheck logs from Realtime
realtime_logs_filtered:
type: filter
inputs:
- router.realtime
condition: '!contains(string!(.event_message), "/health")'
# Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date) # Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
realtime_logs: realtime_logs:
type: remap type: remap
inputs: inputs:
- router.realtime - realtime_logs_filtered
source: |- source: |-
.metadata.project = del(.project) .metadata.project = del(.project)
.metadata.external_id = .metadata.project .metadata.external_id = .metadata.project
@ -825,6 +1022,13 @@ services:
.metadata.context[0].host = parsed.hostname .metadata.context[0].host = parsed.hostname
.metadata.context[0].pid = parsed.pid .metadata.context[0].pid = parsed.pid
} }
# Function logs are unstructured messages on stderr
functions_logs:
type: remap
inputs:
- router.functions
source: |-
.metadata.project_ref = del(.project)
# Postgres logs some messages to stderr which we map to warning severity level # Postgres logs some messages to stderr which we map to warning severity level
db_logs: db_logs:
type: remap type: remap
@ -839,8 +1043,8 @@ services:
if err != null || parsed == null { if err != null || parsed == null {
.metadata.parsed.error_severity = "info" .metadata.parsed.error_severity = "info"
} }
if parsed != null { if parsed.level != null {
.metadata.parsed.error_severity = parsed.level .metadata.parsed.error_severity = parsed.level
} }
if .metadata.parsed.error_severity == "info" { if .metadata.parsed.error_severity == "info" {
.metadata.parsed.error_severity = "log" .metadata.parsed.error_severity = "log"
@ -856,8 +1060,11 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=gotrue.logs.prod'
logflare_realtime: logflare_realtime:
type: 'http' type: 'http'
inputs: inputs:
@ -866,8 +1073,11 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=realtime.logs.prod'
logflare_rest: logflare_rest:
type: 'http' type: 'http'
inputs: inputs:
@ -876,8 +1086,11 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=postgREST.logs.prod'
logflare_db: logflare_db:
type: 'http' type: 'http'
inputs: inputs:
@ -886,21 +1099,24 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
# We must route the sink through kong because ingesting logs before logflare is fully initialised will retry_initial_backoff_secs: 1
# lead to broken queries from studio. This works by the assumption that containers are started in the headers:
# following order: vector > db > logflare > kong x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' uri: 'http://supabase-analytics:4000/api/logs?source_name=postgres.logs'
logflare_functions: logflare_functions:
type: 'http' type: 'http'
inputs: inputs:
- router.functions - functions_logs
encoding: encoding:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=deno-relay-logs&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=deno-relay-logs'
logflare_storage: logflare_storage:
type: 'http' type: 'http'
inputs: inputs:
@ -909,8 +1125,11 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=storage.logs.prod.2'
logflare_kong: logflare_kong:
type: 'http' type: 'http'
inputs: inputs:
@ -920,16 +1139,19 @@ services:
codec: 'json' codec: 'json'
method: 'post' method: 'post'
request: request:
retry_max_duration_secs: 10 retry_max_duration_secs: 30
uri: 'http://supabase-analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}' retry_initial_backoff_secs: 1
headers:
x-api-key: '${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}'
uri: 'http://supabase-analytics:4000/api/logs?source_name=cloudflare.logs.prod'
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
environment: environment:
- LOGFLARE_API_KEY=${SERVICE_PASSWORD_LOGFLARE} - LOGFLARE_PUBLIC_ACCESS_TOKEN=${SERVICE_PASSWORD_LOGFLARE}
command: ["--config", "etc/vector/vector.yml"] command: ["--config", "/etc/vector/vector.yml"]
supabase-rest: supabase-rest:
image: postgrest/postgrest:v12.2.12 image: postgrest/postgrest:v14.6
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database
@ -939,6 +1161,8 @@ services:
environment: environment:
- PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- 'PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public,storage,graphql_public}' - 'PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public,storage,graphql_public}'
- PGRST_DB_MAX_ROWS=${PGRST_DB_MAX_ROWS:-1000}
- PGRST_DB_EXTRA_SEARCH_PATH=${PGRST_DB_EXTRA_SEARCH_PATH:-public}
- PGRST_DB_ANON_ROLE=anon - PGRST_DB_ANON_ROLE=anon
- PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT} - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- PGRST_DB_USE_LEGACY_GUCS=false - PGRST_DB_USE_LEGACY_GUCS=false
@ -947,7 +1171,7 @@ services:
command: "postgrest" command: "postgrest"
exclude_from_hc: true exclude_from_hc: true
supabase-auth: supabase-auth:
image: supabase/gotrue:v2.174.0 image: supabase/gotrue:v2.186.0
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database
@ -975,7 +1199,7 @@ services:
- GOTRUE_DB_DRIVER=postgres - GOTRUE_DB_DRIVER=postgres
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails. # The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails.
- GOTRUE_SITE_URL=${SERVICE_URL_SUPABASEKONG} - GOTRUE_SITE_URL=${GOTRUE_SITE_URL:-${SERVICE_URL_SUPABASEKONG}}
# A comma separated list of URIs (e.g. "https://foo.example.com,https://*.foo.example.com,https://bar.example.com") which are permitted as valid redirect_to destinations. # A comma separated list of URIs (e.g. "https://foo.example.com,https://*.foo.example.com,https://bar.example.com") which are permitted as valid redirect_to destinations.
- GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS} - GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS}
- GOTRUE_DISABLE_SIGNUP=${DISABLE_SIGNUP:-false} - GOTRUE_DISABLE_SIGNUP=${DISABLE_SIGNUP:-false}
@ -1038,7 +1262,7 @@ services:
realtime-dev: realtime-dev:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain # This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
image: supabase/realtime:v2.34.47 image: supabase/realtime:v2.76.5
container_name: realtime-dev.supabase-realtime container_name: realtime-dev.supabase-realtime
depends_on: depends_on:
supabase-db: supabase-db:
@ -1062,6 +1286,7 @@ services:
timeout: 5s timeout: 5s
interval: 5s interval: 5s
retries: 3 retries: 3
start_period: 10s
environment: environment:
- PORT=4000 - PORT=4000
- DB_HOST=${POSTGRES_HOSTNAME:-supabase-db} - DB_HOST=${POSTGRES_HOSTNAME:-supabase-db}
@ -1072,11 +1297,9 @@ services:
- DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime - DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime
- DB_ENC_KEY=supabaserealtime - DB_ENC_KEY=supabaserealtime
- API_JWT_SECRET=${SERVICE_PASSWORD_JWT} - API_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- FLY_ALLOC_ID=fly123
- FLY_APP_NAME=realtime
- SECRET_KEY_BASE=${SECRET_PASSWORD_REALTIME} - SECRET_KEY_BASE=${SECRET_PASSWORD_REALTIME}
- METRICS_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- ERL_AFLAGS=-proto_dist inet_tcp - ERL_AFLAGS=-proto_dist inet_tcp
- ENABLE_TAILSCALE=false
- DNS_NODES='' - DNS_NODES=''
- RLIMIT_NOFILE=10000 - RLIMIT_NOFILE=10000
- APP_NAME=realtime - APP_NAME=realtime
@ -1084,6 +1307,7 @@ services:
- LOG_LEVEL=error - LOG_LEVEL=error
- RUN_JANITOR=true - RUN_JANITOR=true
- JANITOR_INTERVAL=60000 - JANITOR_INTERVAL=60000
- DISABLE_HEALTHCHECK_LOGGING=true
command: > command: >
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server" sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
supabase-minio: supabase-minio:
@ -1121,7 +1345,7 @@ services:
exit 0 exit 0
supabase-storage: supabase-storage:
image: supabase/storage-api:v1.14.6 image: supabase/storage-api:v1.44.2
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database
@ -1160,7 +1384,7 @@ services:
- UPLOAD_FILE_SIZE_LIMIT=524288000 - UPLOAD_FILE_SIZE_LIMIT=524288000
- UPLOAD_FILE_SIZE_LIMIT_STANDARD=524288000 - UPLOAD_FILE_SIZE_LIMIT_STANDARD=524288000
- UPLOAD_SIGNED_URL_EXPIRATION_TIME=120 - UPLOAD_SIGNED_URL_EXPIRATION_TIME=120
- TUS_URL_PATH=upload/resumable - TUS_URL_PATH=/upload/resumable
- TUS_MAX_SIZE=3600000 - TUS_MAX_SIZE=3600000
- ENABLE_IMAGE_TRANSFORMATION=true - ENABLE_IMAGE_TRANSFORMATION=true
- IMGPROXY_URL=http://imgproxy:8080 - IMGPROXY_URL=http://imgproxy:8080
@ -1168,46 +1392,32 @@ services:
- DATABASE_SEARCH_PATH=storage - DATABASE_SEARCH_PATH=storage
- NODE_ENV=production - NODE_ENV=production
- REQUEST_ALLOW_X_FORWARDED_PATH=true - REQUEST_ALLOW_X_FORWARDED_PATH=true
- ANON_KEY=${SERVICE_SUPABASEANON_KEY}
# - ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzA4OTg4NDAwLAogICJleHAiOiAxODY2ODQxMjAwCn0.jCDqsoXGT58JnAjf27KOowNQsokkk0aR7rdbGG18P-8 - SERVICE_KEY=${SERVICE_SUPABASESERVICE_KEY}
# - SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogInNlcnZpY2Vfcm9sZSIsCiAgImlzcyI6ICJzdXBhYmFzZSIsCiAgImlhdCI6IDE3MDg5ODg0MDAsCiAgImV4cCI6IDE4NjY4NDEyMDAKfQ.GA7yF2BmqTzqGkP_oqDdJAQVt0djjIxGYuhE0zFDJV4 - POSTGREST_URL=http://supabase-rest:3000
# - POSTGREST_URL=http://supabase-rest:3000 - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
# - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT} - STORAGE_PUBLIC_URL=${SERVICE_URL_SUPABASEKONG}
# - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - TENANT_ID=${STORAGE_TENANT_ID:-storage-single-tenant}
# - FILE_SIZE_LIMIT=52428800
# - STORAGE_BACKEND=s3
# - STORAGE_S3_BUCKET=stub
# - STORAGE_S3_ENDPOINT=http://supabase-minio:9000
# - STORAGE_S3_PROTOCOL=http
# - STORAGE_S3_REGION=stub
# - STORAGE_S3_FORCE_PATH_STYLE=true
# - AWS_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
# - AWS_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
# - AWS_DEFAULT_REGION=stub
# - FILE_STORAGE_BACKEND_PATH=/var/lib/storage
# - TENANT_ID=stub
# # TODO: https://github.com/supabase/storage-api/issues/55
# - REGION=stub
# - ENABLE_IMAGE_TRANSFORMATION=true
# - IMGPROXY_URL=http://imgproxy:8080
volumes: volumes:
- ./volumes/storage:/var/lib/storage - ./volumes/storage:/var/lib/storage
imgproxy: imgproxy:
image: darthsim/imgproxy:v3.8.0 image: darthsim/imgproxy:v3.30.1
healthcheck: healthcheck:
test: ["CMD", "imgproxy", "health"] test: ["CMD", "imgproxy", "health"]
timeout: 5s timeout: 5s
interval: 5s interval: 5s
retries: 3 retries: 3
environment: environment:
- IMGPROXY_BIND=:8080
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/ - IMGPROXY_LOCAL_FILESYSTEM_ROOT=/
- IMGPROXY_USE_ETAG=true - IMGPROXY_USE_ETAG=true
- IMGPROXY_ENABLE_WEBP_DETECTION=${IMGPROXY_ENABLE_WEBP_DETECTION:-true} - IMGPROXY_AUTO_WEBP=${IMGPROXY_AUTO_WEBP:-true}
- IMGPROXY_MAX_SRC_RESOLUTION=16.8
volumes: volumes:
- ./volumes/storage:/var/lib/storage - ./volumes/storage:/var/lib/storage
supabase-meta: supabase-meta:
image: supabase/postgres-meta:v0.89.3 image: supabase/postgres-meta:v0.95.2
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database
@ -1221,9 +1431,10 @@ services:
- PG_META_DB_NAME=${POSTGRES_DB:-postgres} - PG_META_DB_NAME=${POSTGRES_DB:-postgres}
- PG_META_DB_USER=supabase_admin - PG_META_DB_USER=supabase_admin
- PG_META_DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - PG_META_DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- CRYPTO_KEY=${SERVICE_PASSWORD_PGMETACRYPTO}
supabase-edge-functions: supabase-edge-functions:
image: supabase/edge-runtime:v1.67.4 image: supabase/edge-runtime:v1.71.2
depends_on: depends_on:
supabase-analytics: supabase-analytics:
condition: service_healthy condition: service_healthy
@ -1234,26 +1445,40 @@ services:
retries: 3 retries: 3
environment: environment:
- JWT_SECRET=${SERVICE_PASSWORD_JWT} - JWT_SECRET=${SERVICE_PASSWORD_JWT}
- SUPABASE_URL=${SERVICE_URL_SUPABASEKONG} - SUPABASE_URL=http://supabase-kong:8000
- SUPABASE_PUBLIC_URL=${SERVICE_URL_SUPABASEKONG}
- SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY} - SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_SUPABASESERVICE_KEY} - SUPABASE_SERVICE_ROLE_KEY=${SERVICE_SUPABASESERVICE_KEY}
- SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786 # TODO: Allow configuring VERIFY_JWT per function.
- VERIFY_JWT=${FUNCTIONS_VERIFY_JWT:-false} - VERIFY_JWT=${FUNCTIONS_VERIFY_JWT:-false}
volumes: volumes:
- ./volumes/functions:/home/deno/functions - ./volumes/functions:/home/deno/functions
- deno-cache:/root/.cache/deno
- type: bind - type: bind
source: ./volumes/functions/main/index.ts source: ./volumes/functions/main/index.ts
target: /home/deno/functions/main/index.ts target: /home/deno/functions/main/index.ts
content: | content: |
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts' import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
console.log('main function started') console.log('main function started')
const JWT_SECRET = Deno.env.get('JWT_SECRET') const JWT_SECRET = Deno.env.get('JWT_SECRET')
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true' const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
// Create JWKS for ES256/RS256 tokens (newer tokens)
let SUPABASE_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
if (SUPABASE_URL) {
try {
SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL)
)
} catch (e) {
console.error('Failed to fetch JWKS from SUPABASE_URL:', e)
}
}
function getAuthToken(req: Request) { function getAuthToken(req: Request) {
const authHeader = req.headers.get('authorization') const authHeader = req.headers.get('authorization')
if (!authHeader) { if (!authHeader) {
@ -1266,23 +1491,61 @@ services:
return token return token
} }
async function verifyJWT(jwt: string): Promise<boolean> { async function isValidLegacyJWT(jwt: string): Promise<boolean> {
const encoder = new TextEncoder() if (!JWT_SECRET) {
const secretKey = encoder.encode(JWT_SECRET) console.error('JWT_SECRET not available for HS256 token verification')
try {
await jose.jwtVerify(jwt, secretKey)
} catch (err) {
console.error(err)
return false return false
} }
return true
const encoder = new TextEncoder();
const secretKey = encoder.encode(JWT_SECRET)
try {
await jose.jwtVerify(jwt, secretKey);
} catch (e) {
console.error('Symmetric Legacy JWT verification error', e);
return false;
}
return true;
} }
serve(async (req: Request) => { async function isValidJWT(jwt: string): Promise<boolean> {
if (!SUPABASE_JWT_KEYS) {
console.error('JWKS not available for ES256/RS256 token verification')
return false
}
try {
await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS)
} catch (e) {
console.error('Asymmetric JWT verification error', e);
return false
}
return true;
}
async function isValidHybridJWT(jwt: string): Promise<boolean> {
const { alg: jwtAlgorithm } = jose.decodeProtectedHeader(jwt)
if (jwtAlgorithm === 'HS256') {
console.log(`Legacy token type detected, attempting ${jwtAlgorithm} verification.`)
return await isValidLegacyJWT(jwt)
}
if (jwtAlgorithm === 'ES256' || jwtAlgorithm === 'RS256') {
return await isValidJWT(jwt)
}
return false;
}
Deno.serve(async (req: Request) => {
if (req.method !== 'OPTIONS' && VERIFY_JWT) { if (req.method !== 'OPTIONS' && VERIFY_JWT) {
try { try {
const token = getAuthToken(req) const token = getAuthToken(req)
const isValidJWT = await verifyJWT(token) const isValidJWT = await isValidHybridJWT(token);
if (!isValidJWT) { if (!isValidJWT) {
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), { return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
@ -1348,9 +1611,7 @@ services:
// https://deno.land/manual/getting_started/setup_your_environment // https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc. // This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.177.1/http/server.ts" Deno.serve(async () => {
serve(async () => {
return new Response( return new Response(
`"Hello from Edge Functions!"`, `"Hello from Edge Functions!"`,
{ headers: { "Content-Type": "application/json" } }, { headers: { "Content-Type": "application/json" } },
@ -1367,7 +1628,7 @@ services:
- /home/deno/functions/main - /home/deno/functions/main
supabase-supavisor: supabase-supavisor:
image: 'supabase/supavisor:2.5.1' image: 'supabase/supavisor:2.7.4'
healthcheck: healthcheck:
test: test:
- CMD - CMD
@ -1379,13 +1640,14 @@ services:
timeout: 5s timeout: 5s
interval: 5s interval: 5s
retries: 10 retries: 10
start_period: 30s
depends_on: depends_on:
supabase-db: supabase-db:
condition: service_healthy condition: service_healthy
supabase-analytics: supabase-analytics:
condition: service_healthy condition: service_healthy
environment: environment:
- POOLER_TENANT_ID=dev_tenant - POOLER_TENANT_ID=${POOLER_TENANT_ID:-dev_tenant}
- POOLER_POOL_MODE=transaction - POOLER_POOL_MODE=transaction
- POOLER_DEFAULT_POOL_SIZE=${POOLER_DEFAULT_POOL_SIZE:-20} - POOLER_DEFAULT_POOL_SIZE=${POOLER_DEFAULT_POOL_SIZE:-20}
- POOLER_MAX_CLIENT_CONN=${POOLER_MAX_CLIENT_CONN:-100} - POOLER_MAX_CLIENT_CONN=${POOLER_MAX_CLIENT_CONN:-100}
@ -1402,10 +1664,20 @@ services:
- 'METRICS_JWT_SECRET=${SERVICE_PASSWORD_JWT}' - 'METRICS_JWT_SECRET=${SERVICE_PASSWORD_JWT}'
- REGION=local - REGION=local
- 'ERL_AFLAGS=-proto_dist inet_tcp' - 'ERL_AFLAGS=-proto_dist inet_tcp'
- 'DB_POOL_SIZE=${POOLER_DB_POOL_SIZE:-5}'
# TLS for downstream connections (fixes Supabase CLI TLS requirement)
- GLOBAL_DOWNSTREAM_CERT_PATH=/etc/ssl/server.crt
- GLOBAL_DOWNSTREAM_KEY_PATH=/etc/ssl/server.key
command: command:
- /bin/sh - /bin/sh
- "-c" - "-c"
- '/app/bin/migrate && /app/bin/supavisor eval "$$(cat /etc/pooler/pooler.exs)" && /app/bin/server' - |
if [ ! -f /etc/ssl/server.crt ]; then
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/ssl/server.key -out /etc/ssl/server.crt \
-subj "/CN=supabase-pooler"
fi
/app/bin/migrate && /app/bin/supavisor eval "$$(cat /etc/pooler/pooler.exs)" && /app/bin/server
volumes: volumes:
- type: bind - type: bind
source: ./volumes/pooler/pooler.exs source: ./volumes/pooler/pooler.exs

View file

@ -7,10 +7,10 @@
"version": "4.0.0" "version": "4.0.0"
}, },
"helper": { "helper": {
"version": "1.0.12" "version": "1.0.13"
}, },
"realtime": { "realtime": {
"version": "1.0.11" "version": "1.0.12"
}, },
"sentinel": { "sentinel": {
"version": "0.0.21" "version": "0.0.21"