Use dynamic env vars for Supabase config
Build and Push Docker Image / build (push) Successful in 2m30s Details

Changes $env/static/public to $env/dynamic/public for all Supabase
URL and API key configuration. This allows the app to read environment
variables at runtime instead of build time, enabling deployment with
different configurations without rebuilding the Docker image.

Files updated:
- hooks.server.ts: Use dynamic env for PUBLIC_SUPABASE_URL/KEY
- lib/server/supabase.ts: Lazy-init admin client with dynamic env
- lib/server/storage.ts: Use dynamic env for browser-accessible URLs
- lib/supabase.ts: Use dynamic env for browser client

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-01-26 15:25:41 +01:00
parent c74525e113
commit fdd0bb1f7e
4 changed files with 37 additions and 21 deletions

View File

@ -2,7 +2,7 @@ import pkg from '@supabase/ssr';
const { createServerClient } = pkg; const { createServerClient } = pkg;
import { type Handle, redirect } from '@sveltejs/kit'; import { type Handle, redirect } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks'; import { sequence } from '@sveltejs/kit/hooks';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; import { env as publicEnv } from '$env/dynamic/public';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import type { Database } from '$lib/types/database'; import type { Database } from '$lib/types/database';
import { supabaseAdmin } from '$lib/server/supabase'; import { supabaseAdmin } from '$lib/server/supabase';
@ -11,17 +11,18 @@ import { supabaseAdmin } from '$lib/server/supabase';
let setupCheckCache: { needsSetup: boolean; checkedAt: number } | null = null; let setupCheckCache: { needsSetup: boolean; checkedAt: number } | null = null;
const SETUP_CACHE_TTL = 60000; // 1 minute cache const SETUP_CACHE_TTL = 60000; // 1 minute cache
// Use internal URL for server-side operations (Docker network), fallback to public URL
const SERVER_SUPABASE_URL = env.SUPABASE_INTERNAL_URL || PUBLIC_SUPABASE_URL;
/** /**
* Supabase authentication hook * Supabase authentication hook
* Sets up the Supabase client with cookie handling for SSR * Sets up the Supabase client with cookie handling for SSR
*/ */
const supabaseHandle: Handle = async ({ event, resolve }) => { const supabaseHandle: Handle = async ({ event, resolve }) => {
// Use internal URL for server-side operations (Docker network), fallback to public URL
const supabaseUrl = env.SUPABASE_INTERNAL_URL || publicEnv.PUBLIC_SUPABASE_URL;
const supabaseAnonKey = publicEnv.PUBLIC_SUPABASE_ANON_KEY;
event.locals.supabase = createServerClient<Database>( event.locals.supabase = createServerClient<Database>(
SERVER_SUPABASE_URL, supabaseUrl,
PUBLIC_SUPABASE_ANON_KEY, supabaseAnonKey,
{ {
cookies: { cookies: {
getAll: () => event.cookies.getAll(), getAll: () => event.cookies.getAll(),

View File

@ -1,5 +1,5 @@
import { supabaseAdmin } from './supabase'; import { supabaseAdmin } from './supabase';
import { PUBLIC_SUPABASE_URL } from '$env/static/public'; import { env as publicEnv } from '$env/dynamic/public';
import { import {
S3Client, S3Client,
PutObjectCommand, PutObjectCommand,
@ -18,7 +18,7 @@ export type StorageBucket = 'documents' | 'avatars' | 'event-images';
* This uses PUBLIC_SUPABASE_URL instead of the internal Docker URL * This uses PUBLIC_SUPABASE_URL instead of the internal Docker URL
*/ */
function getBrowserAccessibleUrl(bucket: StorageBucket, path: string): string { function getBrowserAccessibleUrl(bucket: StorageBucket, path: string): string {
return `${PUBLIC_SUPABASE_URL}/storage/v1/object/public/${bucket}/${path}`; return `${publicEnv.PUBLIC_SUPABASE_URL}/storage/v1/object/public/${bucket}/${path}`;
} }
export interface UploadResult { export interface UploadResult {

View File

@ -1,20 +1,22 @@
import pkg from '@supabase/ssr'; import pkg from '@supabase/ssr';
const { createServerClient } = pkg; const { createServerClient } = pkg;
import { createClient as createSupabaseClient } from '@supabase/supabase-js'; import { createClient as createSupabaseClient } from '@supabase/supabase-js';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; import { env as publicEnv } from '$env/dynamic/public';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import type { Cookies } from '@sveltejs/kit'; import type { Cookies } from '@sveltejs/kit';
import type { Database } from '$lib/types/database'; import type { Database } from '$lib/types/database';
// Use internal URL for server-side operations (Docker network), fallback to public URL // Use internal URL for server-side operations (Docker network), fallback to public URL
const SERVER_SUPABASE_URL = env.SUPABASE_INTERNAL_URL || PUBLIC_SUPABASE_URL; function getSupabaseUrl() {
return env.SUPABASE_INTERNAL_URL || publicEnv.PUBLIC_SUPABASE_URL;
}
const SUPABASE_SERVICE_ROLE_KEY = env.SUPABASE_SERVICE_ROLE_KEY || ''; const SUPABASE_SERVICE_ROLE_KEY = env.SUPABASE_SERVICE_ROLE_KEY || '';
/** /**
* Create a Supabase client for server-side operations with cookie handling * Create a Supabase client for server-side operations with cookie handling
*/ */
export function createSupabaseServerClient(cookies: Cookies) { export function createSupabaseServerClient(cookies: Cookies) {
return createServerClient<Database>(SERVER_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { return createServerClient<Database>(getSupabaseUrl(), publicEnv.PUBLIC_SUPABASE_ANON_KEY, {
cookies: { cookies: {
getAll: () => cookies.getAll(), getAll: () => cookies.getAll(),
setAll: (cookiesToSet) => { setAll: (cookiesToSet) => {
@ -26,17 +28,27 @@ export function createSupabaseServerClient(cookies: Cookies) {
}); });
} }
// Lazy-initialized admin client to ensure env vars are loaded
let _supabaseAdmin: ReturnType<typeof createSupabaseClient<Database>> | null = null;
/** /**
* Supabase Admin client with service role key * Supabase Admin client with service role key
* Use this for administrative operations that bypass RLS * Use this for administrative operations that bypass RLS
*/ */
export const supabaseAdmin = createSupabaseClient<Database>( export const supabaseAdmin = new Proxy({} as ReturnType<typeof createSupabaseClient<Database>>, {
SERVER_SUPABASE_URL, get(_, prop) {
SUPABASE_SERVICE_ROLE_KEY, if (!_supabaseAdmin) {
{ _supabaseAdmin = createSupabaseClient<Database>(
auth: { getSupabaseUrl(),
autoRefreshToken: false, env.SUPABASE_SERVICE_ROLE_KEY || '',
persistSession: false {
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
} }
return (_supabaseAdmin as any)[prop];
} }
); });

View File

@ -1,11 +1,14 @@
import pkg from '@supabase/ssr'; import pkg from '@supabase/ssr';
const { createBrowserClient } = pkg; const { createBrowserClient } = pkg;
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; import { env } from '$env/dynamic/public';
import type { Database } from './types/database'; import type { Database } from './types/database';
/** /**
* Create a Supabase client for browser-side operations * Create a Supabase client for browser-side operations
*/ */
export function createClient() { export function createClient() {
return createBrowserClient<Database>(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY); return createBrowserClient<Database>(
env.PUBLIC_SUPABASE_URL,
env.PUBLIC_SUPABASE_ANON_KEY
);
} }