# Functions | Volcano

URL: https://volcano.dev/features/edge-functions

> Run Node.js, Python and Ruby functions on Volcano. Deploy source, get one endpoint answered from the closest region, with the caller's verified identity on the event, live logs and cron schedules.

## Run your code everywhere, on demand

Deploy Node.js, Python or Ruby from source and get one endpoint that answers from the closest region, with the caller's verified identity already on the event.

```console
$ volcano cloud functions deploy --all
Scanning volcano/functions/...
Found 3 function(s)
✓ 3/3 functions deployment started
$ volcano cloud functions invoke summarize --payload '{"id":42}'
200  {"summary":"…"}  · 412 ms
$ volcano cloud functions logs summarize --type runtime --follow
# same endpoint answers from whichever region is closest
```

## The handler is the easy part

Getting a handler to production usually means an image, a registry, a cluster, an autoscaler, a load balancer, a certificate and somewhere to keep the secrets — none of which is the feature you set out to write.

Here you deploy source. Volcano detects the runtime, installs your dependencies, distributes the function to every region, and answers on one endpoint that sends each caller to the closest copy. Secrets arrive as environment variables, logs stream to your terminal, and a cron expression is all a scheduled run needs.

## The caller arrives already verified

*Identity*

Invoke with a user's access token and Volcano validates it, then puts the result on the event as \_\_volcano\_auth. Your handler reads a user id, not a JWT.

- user\_id, email, role and project\_id on the event
- Their access token too, so one function can call another as them
- Sessions reach authenticated functions; anon keys only public ones

```console
{
  user_id:      '8f1c4b0e-…',
  email:        'ada@acme.com',
  role:         'authenticated',
  project_id:   '…',
  access_token: 'eyJhbGciOi…'
}
signature already checked before your code ran
```

## One endpoint, answered from the closest region

*Reach*

Every function gets an HTTPS endpoint deployed to every region Volcano runs in, and a request is answered by the closest one. You publish a single URL and the routing happens for you.

- One URL, no region in the path
- Regions across North America, Europe, Asia-Pacific and South America
- Pin a project to a subset of regions when you need to

one endpoint · answered from the closest region: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-1, ap-northeast-1, ap-southeast-2, sa-east-1.

## Live logs and cron, built in

*Operations*

Build and runtime logs are queryable per function and can be followed live from the CLI. Recurring work is a cron expression attached to a function you already deployed.

- volcano cloud functions logs --type build|runtime --follow
- Five-field UTC cron, down to once a minute
- Scheduled runs arrive with \_\_volcano\_schedule on the event

```console
12:04:01  [us-east-1]  start  request 4c1e…
12:04:01  [us-east-1]  fetched 128 rows
12:04:02  [us-east-1]  done in 940 ms
12:05:00  [eu-west-1]  start  scheduled run
following · ctrl-c to stop
```

## What you get with every function

- **Node, Python and Ruby**: Node.js 22 and 24, Python 3.10 through 3.14, Ruby 3.3, 3.4 and 4.0. The runtime is detected from the file.
- **Long-running work**: Up to 30 seconds per invocation on Hobby or 180 seconds on Superagent, with a writable /tmp. Past that, a durable function runs for up to a year.
- **Deployed to every region**: One HTTPS endpoint per function, answered by the closest region that has a copy.
- **Verified caller identity**: Tokens are validated before your handler runs and arrive on the event as \_\_volcano\_auth.
- **Cron schedulers**: Attach a five-field UTC schedule to a function, from once a minute to once a month.
- **Build and runtime logs**: Query by function and deployment, or follow them live while you reproduce a bug.

## Write it, ship it, schedule it

### Function

```javascript
const { Client } = require('pg');
const { databaseConnectionString } = require('@volcano.dev/sdk');

exports.handler = async (event) => {
  const auth = event.__volcano_auth;
  if (!auth) {
    return { statusCode: 401, body: JSON.stringify({ error: 'sign in required' }) };
  }

  // Connect as the caller so row-level security applies to this query the
  // same way it would from the browser. Omit userId for admin access.
  const db = new Client({
    connectionString: databaseConnectionString(process.env.DATABASE_URL, {
      userId: auth.user_id,
    }),
  });
  await db.connect();
  try {
    const { rows } = await db.query(
      'select title, body from notes where id = $1',
      [event.id],
    );
    if (rows.length === 0) {
      return { statusCode: 404, body: JSON.stringify({ error: 'not found' }) };
    }
    return { statusCode: 200, body: JSON.stringify({ summary: summarize(rows[0]) }) };
  } finally {
    await db.end();
  }
};
```

### Deploy

```bash
# volcano/functions/summarize/index.js  → nodejs24.x  (exports.handler)
# volcano/functions/embed/main.py          → python3.12  (main.handler)
# volcano/functions/report/main.rb         → ruby3.4     (main.handler)
# Files and folders starting with _ are shared code, not functions.

volcano cloud functions deploy --all
volcano cloud functions deploy -f summarize

volcano cloud functions list
volcano cloud functions runtimes

# Functions start private: only service keys and schedulers reach them, and
# to anyone else they answer 404. authenticated lets your signed-in users in,
# anonymous sign-ins included; public also takes an anon key.
volcano cloud functions update summarize --visibility authenticated
# Backend-only: volcano cloud functions update summarize --visibility private
```

### Invoke

```typescript
import { VolcanoAuth } from '@volcano.dev/sdk';

const volcano = new VolcanoAuth({
  apiUrl: process.env.NEXT_PUBLIC_VOLCANO_API_URL!,
  anonKey: process.env.NEXT_PUBLIC_VOLCANO_ANON_KEY!,
});

const { data, status, error } = await volcano.functions.invoke('summarize', {
  id: noteId,
});

// summarize is authenticated, so the signed-in user's token gets it in and
// the handler sees event.__volcano_auth without you assembling a header.
if (error) {
  console.error(status, error.message);
}

// The SDK is a wrapper over an ordinary POST, so anything that speaks HTTP
// can call the same endpoint — CI, another backend, curl. INVOKE_URL
// is the invoke: line from `volcano cloud functions list`; deploy does not
// print it, and it is not on the API's domain, so it cannot be assembled from
// the function's id.
//
//   curl -X POST "$INVOKE_URL" \
//     -H "Authorization: Bearer $TOKEN" \
//     -H "Content-Type: application/json" \
//     -d '{"payload": {"id": 42}}'
//
// The wrapper comes off before your handler runs, which is why it reads
// event.id. In this default mode a token is always required: public means
// an anon key is enough, not that the endpoint is open. For a third-party
// webhook, make the function public with invocation_mode: http and
// http_auth_mode: none, and verify the provider's signature in the handler.
```

### Schedule

```bash
# Five-field UTC cron. Once a minute is the floor, 31 days the ceiling.
volcano cloud functions schedulers create nightly-digest \
  --cron "0 3 * * *" \
  --name digest \
  --payload '{"window":"24h"}'

volcano cloud functions schedulers list nightly-digest
volcano cloud functions schedulers disable nightly-digest <scheduler-id>

# The handler can tell a scheduled run from a request: cron invocations
# arrive with __volcano_schedule on the event.
```

## What people run on them

### Tools an agent can call

One function per capability, each returning JSON and set to authenticated so only signed-in users reach it. The agent's identity travels with the call, so a tool can only touch what that user could.

### Work too heavy for a page

Exports, imports, thumbnails and report builds get up to 30 seconds on Hobby or three minutes on Superagent, plus a scratch disk to work in.

### Scheduled jobs

Nightly digests, hourly re-indexing, a cleanup at the top of the month. Attach cron to the function and Volcano runs it on schedule.

## Works with the rest of Volcano

- [Query as the user](https://volcano.dev/features/postgres): Store the connection string as the DATABASE\_URL project variable and connect as the caller, so their own policies apply.
- [Sessions already verified](https://volcano.dev/features/authentication): Tokens are validated before your handler runs, so there is no verification code to get wrong.
- [Read and write buckets](https://volcano.dev/features/file-storage): Work with storage from a function, or hand the browser a direct upload instead.
- [Work that outlives a request](https://volcano.dev/features/durable-functions): Move multi-step jobs into a durable function that checkpoints each step and resumes after a failure or a wait.

## Frequently asked questions

### Which languages and versions can I use?

Node.js 22 and 24, Python 3.10 through 3.14, and Ruby 3.3, 3.4 and 4.0. The runtime is picked from the entrypoint file, and volcano cloud functions runtimes prints the current list.

### How long can a function run?

Up to 30 seconds per invocation on Hobby or 180 seconds on Superagent, with a writable /tmp for whatever the run produces. Work that takes longer, or has to wait for something, belongs in a durable function: it checkpoints each step and can run for up to a year.

### Where do functions run?

In every region Volcano operates, spanning North America, Europe, Asia-Pacific and South America. Each function has one hostname, and Volcano answers each request from the closest region.

### How do I keep API keys out of my code?

Put them in volcano/volcano.env and run volcano cloud variables deploy. They are injected as environment variables at runtime and never travel in the source archive.

### Can a function run on a schedule?

Yes, with a standard five-field UTC cron expression, as often as once a minute. A scheduled run arrives with \_\_volcano\_schedule on the event, so one handler can serve both requests and cron.

### What stops two overlapping runs doing the same work twice?

A project lock. From a function holding a service key, wrap the work in volcano.locks.withLock('nightly-digest', { ttl: 60 }, run). One run gets the lease and keeps it renewed, and every other run gets acquired: false and skips. For a queue of rows, claim them in Postgres with FOR UPDATE SKIP LOCKED instead.

## Get started

Ready to deploy your first function? Create a free account at https://volcano.dev/signup, or have your coding agent follow the setup playbook at https://volcano.dev/setup.md.
