Skip to content

Functions ​

A function is a single handler, written in Node.js 22 or Python 3.12, that Fjordrun runs behind an HTTPS URL. You write the handler, deploy it, and get a *.fjordrun.app URL. It scales to zero when nobody calls it and is billed per request and per GB-second while it runs.

Use functions for webhooks, small APIs, scheduled callbacks from other services, glue code and anything that answers a request and is done, especially when traffic is rare or comes in bursts. For long-running servers, background workers, apps that keep state in memory, or steady traffic all day, use a service: it bills the CPU and memory it actually uses, which is cheaper when it never sleeps.

A function is a service of kind function underneath: it sits on the project canvas, has an instance in every environment, and works with variables, deployments and rollbacks and logs like any other service.

Create a function ​

In a project, click Create, then Function. Pick a name, the runtime, the memory size and the timeout. Fjordrun creates the function with a hello-world handler and deploys it. Then:

  • Code: edit the files, then Save and deploy. Add a package.json (Node.js) or requirements.txt (Python) for dependencies; they are installed when the function is built.
  • Test: send a request (method, path, headers, body) and see the response, its status and duration.
  • Metrics: invocations, errors, p50 and p95 duration, cold starts and GB-seconds.
  • Logs: what the handler prints, plus one line per invocation.
  • Settings: runtime, memory, timeout, concurrency, scaling, the warm window and access.

From the CLI, deploy a directory (the function is created on the first deploy):

sh
fjordrun functions deploy ./hello --runtime node22 --memory 256 --wait
fjordrun functions invoke hello --path '/?name=Ada'

See the CLI reference and the Functions API.

Write a handler ​

Node.js 22 ​

The entry file (default index.mjs) exports the handler as its default export (or as handler). It gets a standard Request and returns a Response:

js
// index.mjs
export default async (request, context) => {
  const url = new URL(request.url)
  if (request.method === 'POST') {
    const data = await request.json()
    return Response.json({ received: data }, { status: 201 })
  }
  return Response.json({ hello: url.searchParams.get('name') ?? 'world' })
}

The handler can also return a plain object { status, headers, body } (an object body is sent as JSON), a string (sent as text/plain), or any other value (sent as JSON). Returning nothing sends 204 No Content.

context has requestId, functionName, memoryMb, deadline (epoch milliseconds) and remainingMs().

ES modules (.mjs, or .js with "type": "module") and CommonJS (module.exports = async (request) => …) both work. fetch, crypto and the rest of the Node.js standard library are available.

Python 3.12 ​

The entry file (default main.py) defines handler(event, context), a plain function or an async def:

python
# main.py
import json

def handler(event, context):
    if event["method"] == "POST":
        data = json.loads(event["body"] or "{}")
        return {"statusCode": 201, "body": {"received": data}}
    name = event["query"].get("name", "world")
    return {"statusCode": 200, "headers": {"cache-control": "no-store"}, "body": {"hello": name}}

event has method, path, query (a dict), raw_query, headers (lower-case names), body (text) and body_bytes. A body that isn't UTF-8 text arrives base64-encoded in body, with is_base64_encoded set.

Return a dict with statusCode (or status), headers and body (a dict or list body is sent as JSON), a string, bytes, or any other JSON value (sent as a 200 JSON response). Returning None sends 204 No Content.

context has request_id, function_name, memory_mb, deadline (epoch seconds) and remaining_ms().

Synchronous handlers run in a thread pool, async ones on one event loop, so up to the function's concurrency invocations run at the same time on a replica.

Variables, logs and state ​

  • Variables are environment variables (process.env.X, os.environ["X"]), including references to databases and other services.
  • Everything the handler prints goes to the function's logs.
  • Keep state in a database or another service. A replica may be reused for many invocations (module-level connections and caches are fine), but it can be stopped at any time after its idle window.

Settings and limits ​

SettingValuesDefault
Memory128, 256, 512, 1024, 2048 or 4096 MB, up to 512 MB on Trial, 2 GB on Hobby, 4 GB on Pro256 MB
CPU0.5 vCPU per GB reserved, bursting to 1 vCPU (2 vCPU at 4 GB)
Timeout1 second to 60 seconds on Trial, 300 seconds on Hobby, 900 seconds on Pro30 s
Concurrencyinvocations one instance handles at once (1 to 100)10
Max instancesup to 2 on Trial, 10 on Hobby, 50 on Pro1
Stay warm after the last request30 seconds to 15 minutes; fixed at 60 seconds on Trial2 minutes
Accesspublic, or tokenpublic

Other limits:

  • Request body: up to 6 MB.
  • Inline code: up to 256 KB of text in up to 100 files. Larger functions, or ones with binary files, can be deployed from a GitHub repository directory (the function's root directory).
  • Timeout: an invocation still running at its timeout gets a 504. The runtime is then restarted (even a busy loop is stopped), so other invocations running on the same instance at that moment fail with a 502 and the next request is a cold start. Set the timeout above your slowest expected request.
  • Errors: an exception in the handler returns a 500 with the error message; the stack trace is in the logs.
  • Private network: functions are reached through their URL. Requests over the private network (<service>.<env>.internal) don't wake a function that is scaled to zero.

When more requests arrive than the instances can take at their concurrency, Fjordrun adds instances up to the maximum; requests above that wait in line.

Access with a token ​

With Access: Token, every request needs the function's token in the X-Fjordrun-Token header:

sh
curl -H "X-Fjordrun-Token: $TOKEN" https://hello-production-1a2b.fjordrun.app/

Other requests get a 401 and are not billed as invocations. Each environment has its own token: reveal or rotate it in Settings → Access (or with GET /functions/{id}/token). Rotating redeploys the function; the old token stops working once the new deployment is live. The header is removed before the request reaches your handler, and the instances only see a hash of the token.

Cold starts and the warm window ​

A function with no instances running starts one when a request arrives; that first request waits for the cold start. Expect roughly 1 to 3 seconds: the instance is scheduled, the runtime starts and loads your handler. The first start on a server after a deploy also pulls the image, which can add a few seconds. Heavy dependencies loaded at import time make cold starts longer.

After its last request, an instance stays warm for the function's idle window (default 2 minutes) and answers without a cold start. Then it scales to zero. Choose the window in Settings → Resources and scaling (on Trial it is fixed at 60 seconds):

  • Shorter (30 seconds to 1 minute): less instance time to pay for, more cold starts. Good for webhooks and jobs that don't mind a second of latency.
  • Longer (5 to 15 minutes): fewer cold starts for traffic that comes and goes, at the cost of more instance time. For rare requests, a 5-minute window costs about 2.5 times as much as the 2-minute default.

The idle window is billed like the rest of the time an instance is up (see below). A function called at least once per window never scales to zero: a 256 MB function kept up all month is 657,000 GB-seconds, about €3.29.

Pricing ​

All prices in EUR, excluding VAT.

MeterPrice
Requests€0.20 per million
GB-seconds€0.000005 per GB-second (€5 per million)
CPU above the included share€12 per vCPU-month (the normal vCPU rate)
  • GB-seconds are the memory size times the time each instance is up: from its start, through its requests, until its idle window after the last request ends. One instance of 256 MB up for an hour is 0.25 GB × 3,600 s = 900 GB-seconds (€0.0045). An instance that is scaled to zero costs nothing.
  • Each GB-second includes 0.5 vCPU-second. An instance that uses more CPU than that (it can burst to 1 vCPU) pays for the extra at the vCPU rate. Most functions, which wait on the network, never do.
  • Requests refused for a missing or wrong token are not billed. Egress is billed like any service: the first 100 GB per team and billing period are free.

Free every billing period, per team:

PlanRequestsGB-seconds
Trial100,00025,000
Hobby1,000,000200,000
Pro5,000,0001,000,000

Extra CPU is never free.

Function usage also counts against your plan's included usage and your credit, like everything else. See Usage-based pricing for worked examples.

Fjordrun is hosted in the EU (Hetzner, Nuremberg, Germany). Private beta.