Self-Hosting

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.

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:

1
curl http://<your-domain>:8000/functions/v1/hello

This returns "Hello from Edge Functions!".

Create a new function#

Step 1: Create a new directory with an index.ts file in volumes/functions/:#

1
mkdir -p volumes/functions/my-function &&
2
touch volumes/functions/my-function/index.ts

add the following code to index.ts:

1
.(async (: ) => {
2
const { } = await .()
3
const = `Hello, ${}!`
4
5
return new (.({ }), {
6
: { 'Content-Type': 'application/json' },
7
})
8
})

Step 2: Restart the functions service to pick up the new function:#

1
docker compose restart functions --no-deps

Step 3: Invoke your function:#

1
curl -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#

For multiple variables or secrets, create a separate env file, e.g., .env.functions in your docker/ directory:

1
MY_CUSTOM_VAR=some-value

Add env_file to the functions service in docker-compose.yml (variables in env_file load first, then environment values take precedence):

1
functions:
2
env_file:
3
- .env.functions
4
environment:
5
JWT_SECRET: ${JWT_SECRET}
6
SUPABASE_URL: http://kong:8000

Restart the functions service:

1
docker compose up -d --force-recreate --no-deps functions

Using inline environment variables#

For one or two variables, you can add them directly under environment in docker-compose.yml:

1
functions:
2
environment:
3
# Custom variables
4
MY_CUSTOM_VAR: ${MY_CUSTOM_VAR}
5
# Required variables
6
JWT_SECRET: ${JWT_SECRET}
7
SUPABASE_URL: http://kong:8000

Then 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:

1
const = ..('MY_CUSTOM_VAR')

Calling Supabase services from functions#

The functions service is pre-configured with the following environment variables:

VariableValuePurpose
SUPABASE_URLhttp://kong:8000Internal API gateway URL
SUPABASE_PUBLIC_URLhttp://<your-domain>:8000Base URL for accessing Supabase from the Internet
JWT_SECRETYour secret keyLegacy symmetric encryption key used to sign and verify JWTs
SUPABASE_ANON_KEYYour anon keyClient-side API key with limited permissions (anon role).
SUPABASE_SERVICE_ROLE_KEYYour service role keyServer-side API key with full database access (service_role role)
SUPABASE_DB_URLPostgres connection stringCan be used for direct database access

Here's an example function that queries a table using @supabase/supabase-js:

1
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
2
3
Deno.serve(async () => {
4
const supabase = createClient(
5
Deno.env.get('SUPABASE_URL')!,
6
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
7
)
8
9
const { data, error } = await supabase.from('todos').select('*')
10
11
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_URL contains 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_URL is 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:

1
scp -r ./my-function user@<your-domain>:/path/to/self-hosted/volumes/functions/

Then restart the functions service on the remote host:

1
ssh 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:

1
docker compose logs functions

Common causes: syntax errors in your function code, invalid imports, or missing dependencies.

401 "invalid JWT"#

  • Check that FUNCTIONS_VERIFY_JWT matches your intent (true or false) 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:

1
docker compose restart functions --no-deps

Custom env vars not available in functions#

  • Verify the variable is defined in docker-compose.yml (under env_file or environment)
  • 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:

1
docker compose up -d --force-recreate --no-deps functions

Memory 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.