Appearance
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) orrequirements.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
| Setting | Values | Default |
|---|---|---|
| Memory | 128, 256, 512, 1024, 2048 or 4096 MB, up to 512 MB on Trial, 2 GB on Hobby, 4 GB on Pro | 256 MB |
| CPU | 0.5 vCPU per GB reserved, bursting to 1 vCPU (2 vCPU at 4 GB) | |
| Timeout | 1 second to 60 seconds on Trial, 300 seconds on Hobby, 900 seconds on Pro | 30 s |
| Concurrency | invocations one instance handles at once (1 to 100) | 10 |
| Max instances | up to 2 on Trial, 10 on Hobby, 50 on Pro | 1 |
| Stay warm after the last request | 30 seconds to 15 minutes; fixed at 60 seconds on Trial | 2 minutes |
| Access | public, or token | public |
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 a502and the next request is a cold start. Set the timeout above your slowest expected request. - Errors: an exception in the handler returns a
500with 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.
| Meter | Price |
|---|---|
| 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:
| Plan | Requests | GB-seconds |
|---|---|---|
| Trial | 100,000 | 25,000 |
| Hobby | 1,000,000 | 200,000 |
| Pro | 5,000,000 | 1,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.