This feature is part of Cosmos Pro. Install it with get-pro.sh and enter your licence key in Configuration > General.

Cloud Functions

A function is a small piece of code that answers on a URL, without you having to think about a server, a container or a Dockerfile. You write a function in JavaScript or Python, publish it as a package, and Cosmos gives it a URL.

Functions cost nothing while nobody calls them: they sleep when idle, wake up on the next request, and spread on more of your nodes when the traffic grows. They are ideal for webhooks, small APIs, form handlers, scheduled jobs and all the glue code that does not deserve a full application.

Before you start

  • Functions run on your cluster, so Constellation must be enabled. A single server works.
  • The code of a function lives in a Package Registry: create an npm registry for JavaScript functions, or a PyPI registry for Python ones.
  • Your nodes need internet access: the dependencies of your functions are downloaded from the public npm or PyPI.

Your first function

Let's make a JavaScript function that says hello. We assume an npm registry answering on npm.mydomain.com, and one of its deploy tokens in your .npmrc (see npm).

1. Write it

A function is a regular npm package. It exports a function that receives the request and the response, in the same style as Express:

index.js

exports.hello = (req, res) => {
  const name = req.query.name || "world";
  res.json({ message: `Hello ${name}!` });
};

package.json

{
  "name": "@mycompany/hello",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {}
}

2. Publish it

npm publish --registry https://npm.mydomain.com/

3. Create the function

Go to Constellation > Functions and click Create.

  • Function name: hello
  • Runtime: Node.js 22
  • Registry: your npm registry
  • Package: @mycompany/hello
  • Handler: hello, the name of the exported function

Leave the URL section as suggested and click Create.

4. Call it

curl "https://functions.mydomain.com/hello?name=Cosmos"
{"message":"Hello Cosmos!"}

The very first call takes a little longer, while the function installs its dependencies. After that, waking up a sleeping function only takes a moment.

Writing functions

Any HTTP request to the URL of the function calls your handler, whatever the method and the sub-path. The part of the path that identifies the function is removed: a call to /hello/users/12 reaches your code as /users/12.

JavaScript

Available runtimes: Node.js 22 and Node.js 20.

The handler receives (req, res), like an Express route: req.method, req.path, req.query, req.headers, req.body (already parsed for JSON), and res.status(), res.json(), res.send().

exports.contact = async (req, res) => {
  if (req.method !== "POST") {
    return res.status(405).send("Method not allowed");
  }
  await saveMessage(req.body.email, req.body.message);
  res.json({ ok: true });
};

The handler is looked up in the main file of the package. List what you need in the dependencies of your package.json, it is installed automatically.

Python

Available runtimes: Python 3.12 and Python 3.11.

The handler receives a Flask request, and returns anything Flask accepts: a string, a dictionary (sent as JSON), or a (body, status) pair.

my_function/__init__.py

import os

def hello(request):
    name = request.args.get("name", "world")
    return {"message": f"Hello {name}!", "greeting": os.environ.get("GREETING", "")}

Package it like any Python project (a pyproject.toml with its dependencies), then build and publish it:

python -m build
twine upload --repository-url https://pypi.mydomain.com/ \
  -u __token__ -p "$COSMOS_REGISTRY_TOKEN" dist/*

The handler is looked up in the module named after the package (my-function becomes my_function).

Several functions in one package

A package can export as many handlers as you want. Create one function per handler, all pointing to the same package: each gets its own name, URL and schedule.

The create form

  • Function name: letters and digits. It identifies the function, and is the default path of its URL.
  • Runtime: the language and version.
  • Registry, Package, Handler: where the code is, and which exported function to call. The package must be published before you create the function.
  • URL: by default all your functions share one Hostname, functions. followed by your domain, and each has its own Path, / followed by its name. You can change both: give a function its own hostname and empty the path, and it owns the whole hostname. Like any other URL, the hostname must point to your server.
  • Environment: variables passed to your code. The template variables of deployments work here, which is the easy way to connect a function to a managed database or an object storage:

| Key | Value | |---|---| | DATABASE_URL | ${db.main.app.url} | | S3_ENDPOINT | ${s3.media.endpoint} | | S3_ACCESS_KEY | ${s3.media.accessKey} | | S3_SECRET_KEY | ${s3.media.secretKey} |

The Advanced section is fine on its defaults:

  • Entry: the file (JavaScript) or module (Python) that exports the handler, when it is not the main one. For example lib/api.js or my_function.api.
  • Version: the version of the package to deploy. Empty means the latest published.
  • Tags: only the nodes carrying all these tags can run the function. Empty means any node.
  • Memory (MB): 256 by default. CPUs: no limit by default.
  • Timeout (seconds): 60 by default. Requests running longer are cut.
  • Idle before sleep: 1h by default. How long a function stays awake without being called (30m, 2h...).
  • Runtime image override: use your own runtime image, for example from a mirror.

Your code can also read COSMOS_FUNCTION (the name of the function), COSMOS_FUNCTION_REV (the release number) and COSMOS_NODE (the node it runs on).

Sleeping, waking up and scaling

One copy of the function is always placed on a node, ready to go, and asleep when idle: it uses no CPU and no memory. The next request wakes it up, and Cosmos holds that request until the function is ready. This is the same mechanism as lazy containers.

When the nodes running the function get busy, Cosmos starts copies on more nodes, up to one per eligible node, and spreads the requests between them. When the load drops, the extra copies go away.

The URL of a function answers from any node of your cluster.

Securing the URL

A new function is public, protected by the SmartShield and the bot blocker. The URL tab of the function is the usual Cosmos URL form: from there you can require a login, restrict the function to your Constellation or to a list of IPs, tune the SmartShield, or set CORS.

For a function called by an outside service (a payment provider, a git webhook), keep it public and check a secret in your code. For a function only called by your own applications, restrict it to the Constellation.

Shipping a new version

Publishing a new version of the package does not change what is running: a function always stays on the exact version it was deployed with, until you decide otherwise.

Open the function, and in the Releases card of the Overview tab click Deploy. Choose Latest published version or a specific one. Every copy of the function is replaced with the new version.

The Releases card keeps the history of the deployments, with who did them and when. Roll back next to a release puts that version back in a click.

If your code is in a git repository, CI/CD can publish and deploy your functions on every push.

The Edit tab changes everything else (runtime, package, handler, environment, limits), and the Test invocation card of the Overview tab lets you call the function right from the interface, choosing the method, the path and the body, and shows the answer with its duration.

Running on a schedule

The Triggers tab calls your function on a schedule. Click Add:

  • Name: what this schedule is for (nightly-cleanup).
  • Schedule: a crontab with seconds first. 0 0 * * * * is every hour, 0 0 3 * * * every night at 3.
  • Method, Path and Body: the request to send. The body is sent as JSON.

The runs and their results appear in the Scheduler page, under Functions. A run is marked as failed when the function answers with an error.

Scheduled calls go through the URL of the function, so the URL must not require a login. To keep a scheduled function private, restrict its URL to the Constellation instead.

Monitoring and events

Each function card shows its status, its version, the nodes holding a copy and its URL. On the function page, the Monitoring tab graphs the number of copies, the invocations (successes, errors, response time, data) and the CPU and memory used. The Events tab keeps the history: created, deployed, updated.

What your code prints (console.log, print) goes to the logs of the container named fn- followed by the function name, visible in the ServApps page of the node running it.

Deleting a function

The Danger zone card of the Overview tab removes the function from every node, along with its URL. The package stays in the registry.

Functions are deployments

Behind the scenes, a function is a deployment named fn followed by its name, using the Fill replica mode with the Empty fill mode. You will see it in the Deployments page, with its replicas and its events.

You can also write it yourself: in the deployment form, switch Specification from Compose to Function and describe the package and all its handlers at once. Each handler becomes a function of the Functions page, and they are all deployed and rolled back together:

{
  "runtime": "node22",
  "source": { "registry": "npm", "package": "@mycompany/api" },
  "env": { "DATABASE_URL": "${db.main.app.url}" },
  "limits": { "memoryMB": 256, "timeoutSec": 60, "idleTTL": "1h" },
  "handlers": [
    {
      "name": "hello",
      "handler": "hello",
      "route": {
        "Host": "functions.mydomain.com",
        "PathPrefix": "/hello",
        "StripPathPrefix": true,
        "BlockCommonBots": true,
        "SmartShield": { "Enabled": true }
      }
    },
    {
      "name": "cleanup",
      "handler": "cleanup",
      "entry": "lib/jobs.js",
      "route": {
        "Host": "functions.mydomain.com",
        "PathPrefix": "/cleanup",
        "StripPathPrefix": true,
        "RestrictToConstellation": true
      },
      "triggers": {
        "cron": [
          { "name": "nightly", "enabled": true, "crontab": "0 0 3 * * *", "method": "POST", "path": "/", "body": "{}" }
        ]
      }
    }
  ]
}

Choose the Fill replica mode with the Empty fill mode to get the same sleep and scale behaviour as the functions created from the Functions page.