Self-Hosted Functions
Run and manage Edge Functions in your self-hosted Supabase instance.
Edge Functions work out of the box in a self-hosted Supabase setup. The functions service, API gateway routing, and a hello example function are all pre-configured.
On managed Supabase platform, Edge Functions are deployed across multiple regions. Self-hosted standalone instance configuration resembles a standard serverless setup.
Invoke the default function#
The default hello function is located at volumes/functions/hello/index.ts. You can invoke it immediately after starting your stack:
1curl http://<your-domain>:8000/functions/v1/helloThis returns "Hello from Edge Functions!".
Create a new function#
Step 1: Create a new directory with an index.ts file in volumes/functions/:#
1mkdir -p volumes/functions/my-function &&2touch volumes/functions/my-function/index.tsadd the following code to index.ts:
1.(async (: ) => {2 const { } = await .()3 const = `Hello, ${}!`45 return new (.({ }), {6 : { 'Content-Type': 'application/json' },7 })8})Step 2: Restart the functions service to pick up the new function:#
1docker compose restart functions --no-depsStep 3: Invoke your function:#
1curl -X POST http://<your-domain>:8000/functions/v1/my-function \2 -H 'Content-Type: application/json' \3 -d '{"name": "World"}'You should be able to see the response from my-function:
1{"message":"Hello, World!"}Custom environment variables#
Using an env file (recommended)#
For multiple variables or secrets, create a separate env file, e.g., .env.functions in your docker/ directory:
1MY_CUSTOM_VAR=some-valueAdd env_file to the functions service in docker-compose.yml (variables in env_file load first, then environment values take precedence):
1functions:2 env_file:3 - .env.functions4 environment:5 JWT_SECRET: ${JWT_SECRET}6 SUPABASE_URL: http://kong:8000Don't commit .env.functions to version control if it contains secrets. Add it to your .gitignore.
Restart the functions service:
1docker compose up -d --force-recreate --no-deps functionsUsing inline environment variables#
For one or two variables, you can add them directly under environment in docker-compose.yml:
1functions:2 environment:3 # Custom variables4 MY_CUSTOM_VAR: ${MY_CUSTOM_VAR}5 # Required variables6 JWT_SECRET: ${JWT_SECRET}7 SUPABASE_URL: http://kong:8000Then define MY_CUSTOM_VAR in your main .env file, or specify the value directly.
Accessing variables in functions#
All container environment variables are forwarded to function workers by main/index.ts. Access them with:
1const = ..('MY_CUSTOM_VAR')Calling Supabase services from functions#
The functions service is pre-configured with the following environment variables:
| Variable | Value | Purpose |
|---|---|---|
SUPABASE_URL | http://kong:8000 | Internal API gateway URL |
SUPABASE_PUBLIC_URL | http://<your-domain>:8000 | Base URL for accessing Supabase from the Internet |
JWT_SECRET | Your secret key | Legacy symmetric encryption key used to sign and verify JWTs |
SUPABASE_ANON_KEY | Your anon key | Client-side API key with limited permissions (anon role). |
SUPABASE_SERVICE_ROLE_KEY | Your service role key | Server-side API key with full database access (service_role role) |
SUPABASE_DB_URL | Postgres connection string | Can be used for direct database access |
Here's an example function that queries a table using @supabase/supabase-js:
1import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'23Deno.serve(async () => {4 const supabase = createClient(5 Deno.env.get('SUPABASE_URL')!,6 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!7 )89 const { data, error } = await supabase.from('todos').select('*')1011 return new Response(JSON.stringify({ data, error }), {12 headers: { 'Content-Type': 'application/json' },13 })14})Internal vs external URLs#
This is a key distinction that affects how you build URLs in your functions:
-
SUPABASE_URLcontains an internal Docker network hostname. Use it for server-side calls from your functions to other Supabase services (Auth, Storage, database via PostgREST). This is what the Supabase JS client should use inside functions. -
SUPABASE_PUBLIC_URLis the externally-reachable URL of your Supabase instance (e.g.,<your-domain>:8000). Use it if your function needs to build URLs that HTTP clients can reach from the outside.
Managing functions via dashboard#
Self-hosted Studio mounts the same volumes/functions directory as the functions service. You can check what functions are available using Edge Functions > Functions UI.
Deploying functions to a remote server#
To deploy a function to a remote server running self-hosted Supabase, copy the function directory with scp:
1scp -r ./my-function user@<your-domain>:/path/to/self-hosted/volumes/functions/Then restart the functions service on the remote host:
1ssh user@<your-domain> 'cd /path/to/self-hosted && docker compose restart functions --no-deps'Copying functions from Supabase platform#
If you have existing functions on Supabase platform, you can download them and run them on your self-hosted instance. There are two ways to get the function source code:
- Dashboard - open the function details in Dashboard and click Download.
- Local development & CLI - run
supabase functions download <function-name> --project-ref <ref>to download the source.
Use scp to copy the function into volumes/functions/<function-name>/ on your self-hosted instance, then restart the functions service.
For more details, see:
Troubleshooting#
400 "missing function name in request"#
The request URL must include the function name after /functions/v1/. For example, /functions/v1/hello — not just /functions/v1/.
500 error on invocation#
Check the functions service logs:
1docker compose logs functionsCommon causes: syntax errors in your function code, invalid imports, or missing dependencies.
401 "invalid JWT"#
- Check that
FUNCTIONS_VERIFY_JWTmatches your intent (trueorfalse) in.env - If verification is enabled, ensure you're passing a valid token:
Authorization: Bearer <anon_key or service_role_key>
Changes to function code not reflected after editing#
Restart the functions service:
1docker compose restart functions --no-depsCustom env vars not available in functions#
- Verify the variable is defined in
docker-compose.yml(underenv_fileorenvironment) - Recreate the functions container after changing configuration
- Check that the variable name matches exactly (case-sensitive)
Use the following command to recreate the container, not just restart:
1docker compose up -d --force-recreate --no-deps functionsMemory or timeout errors#
The default limits are 150 MB memory and 60 seconds timeout per function invocation. These are set in volumes/functions/main/index.ts. To adjust them, edit the memoryLimitMb and workerTimeoutMs values and restart the functions service.