This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Runtimes

Apache OpenServerless (OPS) Runtimes Explained

Apache OpenServerless (OPS) is a serverless platform built on Apache OpenWhisk, designed to execute functions in a scalable, event-driven environment. OPS leverages OpenWhisk’s runtime model while extending its capabilities to support additional customization via Docker.


Overview of OPS Runtimes

A runtime in OPS is a preconfigured environment that executes a serverless function. Since serverless platforms allocate compute resources dynamically, a runtime ensures:

  1. Portability – Developers can write code in different languages without managing dependencies.
  2. Scalability – The system provisions and scales runtimes automatically based on demand.
  3. Isolation – Each Action runs within its own dedicated runtime environment, ensuring security and consistency.
  4. Resource Efficiency – The platform optimizes resource allocation by suspending inactive runtimes and reusing active ones when possible.

OPS natively supports the following runtimes:

  • Python
  • Node.js
  • PHP

However, since OPS is built on OpenWhisk, it inherits compatibility with all OpenWhisk-supported runtimes (e.g., Java, Go) and allows users to define custom runtimes using Docker containers.

For greater flexibility, developers can also package their own runtime environments using Docker to create “black box” actions.


How OPS Runtimes Work

1. Runtime Lifecycle

OPS follows OpenWhisk’s runtime model, where each function invocation occurs in an isolated container. The lifecycle includes:

  1. Initialization: A container is provisioned with the selected runtime.
  2. Execution: The function code runs within the container.
  3. Idle: The container is paused (but retained) for reuse (Warm Start).
  4. Destroying: Idle containers are garbage-collected after some time.

2. Cold vs. Warm Starts

  • Cold Start: A new container is created, increasing latency.
  • Warm Start: Reuses a paused container for faster execution.

3. Runtime Composition

Each runtime includes:

  • Language Interpreter/Compiler: (e.g., Python 3.12, Node.js 21).
  • Action Interface: A proxy that implements a canonical protocol to integrate with the OpenWhisk platform.
  • Dependencies: Preinstalled libraries (e.g., requests for Python).

Actions

Actions are the fundamental execution units in Apache OpenServerless. They are stateless functions that run on the OpenWhisk platform.

An action can be used to update a database, respond to an API call, communicate with another system, ecc.

To use a function as an action, it must conform to the following:

  • The function accepts a dictionary as input and produces a dictionary as output. The input and output dictionaries are key-value pairs, where the key is a string and the value is any valid JSON value. The dictionaries are canonically represented as JSON objects when interfacing to an action via the REST API or the ops CLI.

  • The function must be called main or exposed as main

1 - NodeJS

Creating and invoking JavaScript actions

The process of creating JavaScript actions is similar to that of other actions. The following sections guide you through creating and invoking a single JavaScript action, and demonstrate how to bundle multiple JavaScript files and third party dependencies.

  1. Create a package directory. Create a JavaScript file with the following content inside our packages/nodejs. For this example, the file name is hello.js.
//--web true
//--kind nodejs:default
function main() {
    return { msg: 'Hello world' };
}

The JavaScript file might contain additional functions. However, by convention, a function called main must exist to provide the entry point for the action.

You directory structure should looks like this:

nodejs_app
└── packages
    └── nodejs
        └── hello.js
  1. Create an action from the following JavaScript function. For this example, the action is called hello.
/home/openserverless/.ops/tmp/deploy.pid
PID 278075
> Scan:
>> Action: packages/nodejs/hello.js
> Deploying:
>> Package: nodejs
$ $OPS package update nodejs 
ok: updated package nodejs
>>> Action: packages/nodejs/hello.js
$ $OPS action update nodejs/hello packages/nodejs/hello.js --web true --kind nodejs:default
ok: updated action nodejs/hello
build process exited with code 0
UPLOAD ASSETS FROM web
==================| UPLOAD RESULTS |==================
| FILES      : 0
| COMPLETED  : 0
| ERRORS     : 0
| SKIPPED    : 0
| EXEC. TIME : 2.37 ms
======================================================
URL: http://opstutorial.localhost:80

Note: To use a specific version of NodeJs runtime, change the kind property --kind nodejs:18, or --kind nodejs:20 in the hello.js file.

Creating asynchronous actions

JavaScript functions that run asynchronously may need to return the activation result after the main function has returned. You can accomplish this by returning a Promise in your action.

  1. Save the following content in a file called asyncAction.js inside the folder packages/nodejs.
//--web true
//--kind nodejs:default
function main(args) {
     return new Promise(function(resolve, reject) {
       setTimeout(function() {
         resolve({ done: true });
       }, 2000);
    })
 }

Notice that the main function returns a Promise, which indicates that the activation hasn’t completed yet, but is expected to in the future.

The setTimeout() JavaScript function in this case waits for two seconds before calling the callback function. This represents the asynchronous code and goes inside the Promise’s callback function.

The Promise’s callback takes two arguments, resolve and reject, which are both functions. The call to resolve() fulfills the Promise and indicates that the activation has completed normally.

A call to reject() can be used to reject the Promise and signal that the activation has completed abnormally.

  1. Run the following commands to create the action and invoke it:
ops ide deploy
ops action invoke nodejs/asyncAction --result
{
    "done": true
}

Notice that you performed a blocking invocation of an asynchronous action.

  1. Fetch the activation log to see how long the activation took to complete:
ops activation list --limit 1 nodejs/asyncAction
Datetime            Activation ID                    Kind      Start Duration   Status  Entity
2024-03-27 19:46:43 64581426b44e4b3d981426b44e3b3d19 nodejs:21  cold  2.033s     success openserverless/asyncAction:0.0.1
ops activation get 64581426b44e4b3d981426b44e3b3d19
 {
     ...
     "start": 1743101268649,
     "end":   1743101270964,
     ...
 }

Comparing the start and end time stamps in the activation record, you can see that this activation took slightly over two seconds to complete.

Using actions to call an external API

The examples so far have been self-contained JavaScript functions. You can also create an action that calls an external API.

This example invokes a Yahoo Weather service to get the current conditions at a specific location.

  1. Save the following content in a file called weather.js.
const fetch = require('node-fetch')

const getWeatherForecast = async (latitude, longitude) => {
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m`

  try {
    const response = await fetch(url)
    if (!response.ok) {
      throw new Error('Error during the request')
    }
    const data = await response.json()
    return data
  } catch (error) {
    console.error('Error:', error)
    return JSON.Stringify(error)
  }
};

function main(args) {
  const {latitude, longitude} = args

  return await getWeatherForecast(latitude, longitude)
}

Note that the action in the example uses a Node.js library to fetch forecast data. However, you can use a library available in runtime dependencies or you can add a package.json file and specify the action’s dependencies.

Check a specific version of the Node.js runtime for packages available in the runtime environment.

This example also shows the need for asynchronous actions. The action returns uses ‘async/await’ to fetch the data from another system. When the action is completed, the action will return the values from the other sistem.

  1. Create an action from the weather.js file:
ops ide deploy
  1. Use the following command to run the action, and observe the output:
ops action invoke nodejs/weather --param latitude "51.509865" --param longitude "-0.118092" --result

Using the --result flag means that the value returned from the action is shown as output on the command-line:

{
  "elevation": 21,
  "generationtime_ms": 0.03039836883544922,
  "hourly": {
      "temperature_2m": [
          12.8,
          12.9,
          ...
      ],
      "time": [
          "2025-03-27T00:00",
          "2025-03-27T01:00",
          ...
      ]
  },
  "hourly_units": {
      "temperature_2m": "°C",
      "time": "iso8601"
  },
  "latitude": 51.5,
  "longitude": -0.120000124,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "utc_offset_seconds": 0
}

This example also passed a parameter to the action by using the --param flag and a value that can be changed each time the action is invoked.

Packaging actions as Node.js modules with NPM libraries

Instead of writing all your action code in a single JavaScript source file, actions can be deployed from a zip file containing a Node.js module.

Archive zip files are extracted into the runtime environment and dynamically imported using require() during initialisation. Actions packaged as a zip file MUST contain a valid package.json with a main field used to denote the module index file to return.

ops ide deploy will include automatically node_modules folder in a zip file means external NPM libraries can be used on the platform.

Note: remember that each runtime has a set of dependecies already installed. if it’s possible use this set of libraries. It’s better to don’t load too much external libraries because it will deteriorate the action’s performance

Simple Example

  • Create a folder called packageAction inside packages/nodejs folder

  • Create the following package.json file:

{
  "name": "my-action",
  "main": "index.js",
  "dependencies" : {
    "left-pad" : "1.1.3"
  }
}
  • Create the following index.js file:
//--web true
//--kind nodejs:default
const leftPad = require("left-pad")
function main (args) {
  const lines = args.lines || [];
  return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
  • Now you should have this folder structure:

    nodejs_app
    └── packages
        └── nodejs
            └── packageAction
               ├── hello.js
               └── package.json
    
  • Create the action

ops ide deploy

When creating an action from a folder, ops ide deploy will create automatically the following artifacts:

  • node_modules
  • package-lock.json
  • <foldername>.zip

the zip fill is used by ops to create/update the function in your ops environment

  • Invoke the action as normal.
ops action invoke nodejs/packageAction --param lines "[\"and now\", \"for something completely\", \"different\" ]" --result 
{
    "padded": [
        ".......................and now",
        "......for something completely",
        ".....................different"
    ]
}

Using JavaScript Bundlers to package action source files

Using a JavaScript module bundler can transform application source files (with external dependencies) into a single compressed JavaScript file. This can lead to faster deployments, lower cold-starts and allow you to deploy large applications where individual sources files in a zip archive are larger than the action size limit.

Here are the instructions for how to use three popular module bundlers with the Node.js runtime. The “left pad” action example will be used as the source file for bundling along with the external library.

Using rollup.js

In this example we will use rollupjs to deliver the action

  • Create rollupAction folder inside packages/nodejs. Then re-write the index.js to use ES6 Modules, rather than CommonJS module format. Create the package.json and copy the content from the previous example
import leftPad from 'left-pad';

function myAction(args) {
  const lines = args.lines || [];
  return { padded: lines.map(l => leftPad(l, 30, ".")) }
}

export const main = myAction

Make sure you export the function using the const main = ... pattern. Using export {myAction as main} does not work due to tree-shaking. See this blog post for full details on why this is necessary.

  • Create the Rollup.js configuration file in rollup.config.js with the following contents.
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';

export default {
  input: 'index.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  },
  plugins: [
    resolve(),
    commonjs()
  ]
};

Note: run the following command inside the rollupAction folder

  • Install the Rollup.js library and plugins using NPM.
npm install rollup rollup-plugin-commonjs rollup-plugin-node-resolve --save-dev 
  • Run the Rollup.js tool using the configuration file.
npx rollup --config 
  • Create an action using the bundle source file.
ops action create nodejs/rollupAction bundle.js --kind nodejs:20
  • Invoke the action as normal. Results should be the same as the example above.
ops action invoke nodejs/rollupAction --result --param lines "[\"and now\", \"for something completely\", \"different\" ]"

Using webpack

In this example we will use webpack to deliver the action

  • Create webpackAction folder inside packages/nodejs. Then re-write the index.js to export the main function using as a global reference. Then, create the package.json and copy the content from the previous example
const leftPad = require('left-pad');

function myAction(args) {
  const lines = args.lines || [];
  return { padded: lines.map(l => leftPad(l, 30, ".")) }
}

global.main = myAction

This allows the bundle source to “break out” of the closures Webpack uses when defining the modules.

  • Create the Webpack configuration file in webpack.config.js with the following contents.
module.exports = {
  entry: './index.js',
  target: 'node',
  output: {
    filename: 'bundle.js'
  }
};
  • Install the Webpack library and CLI using NPM.
npm install webpack-cli --save-dev
  • Run the Webpack tool using the configuration file.
npx webpack --config webpack.config.js
  • Create an action using the bundle source file.
ops action create nodejs/webpackAction dist/bundle.js --kind nodejs:21
  • Invoke the action as normal. Results should be the same as the example above.
ops action invoke nodejs/webpackAction --result --param lines "[\"and now\", \"for something completely\", \"different\" ]"

1.1 - nodejs:v18

nodejs:v18 (default: false)

LibraryVersionLink
@azure/openai^1.0.0-beta.11npm
@langchain/community^0.0.34npm
minio^7.1.3npm
mongodb^6.4.0npm
node-auth0^1.0.0npm
ollama^0.5.0npm
openai^4.28.4npm
openwhisk^3.21.8npm
pg^8.11.3npm
plotly^1.0.6npm
redis^4.6.13npm
uuid^9.0.1npm

1.2 - nodejs:v20

nodejs:v20 (default: false)

LibraryVersionLink
@azure/openai^1.0.0-beta.11npm
@langchain/community^0.0.34npm
minio^7.1.3npm
mongodb^6.4.0npm
node-auth0^1.0.0npm
ollama^0.5.0npm
openai^4.28.4npm
openwhisk^3.21.8npm
pg^8.11.3npm
plotly^1.0.6npm
redis^4.6.13npm
uuid^9.0.1npm

1.3 - nodejs:v21

nodejs:v21 (default: true)

LibraryVersionLink
@azure/openai^1.0.0-beta.11npm
@langchain/community^0.0.34npm
minio^7.1.3npm
mongodb^6.4.0npm
node-auth0^1.0.0npm
ollama^0.5.0npm
openai^4.28.4npm
openwhisk^3.21.8npm
pg^8.11.3npm
plotly^1.0.6npm
redis^4.6.13npm
uuid^9.0.1npm

2 - PHP

Creating and invoking PHP actions

The process of creating PHP actions is similar to that of other actions. The following sections guide you through creating and invoking a single PHP action, and demonstrate how to bundle multiple PHP files and third party dependencies.

  1. Create a Python_app folder and then create a package directory.Now create a Python file with the following content inside our packages/php. For this example, the file name is hello.php.
<?php
//--web true
//--kind php:default
function main(array $args) : array
{
  $name = $args["name"] ?? "stranger";
  $greeting = "Hello $name!";
  echo $greeting;
  return ["greeting" => $greeting];
}
?>

The PHP file might contain additional functions. However, by convention, a function called main must exist to provide the entry point for the action.

Your directory structure should looks like this:

PHP_app
└── packages
    └── php
        └── hello.php
  1. Run the following command to deploy the action
ops ide deploy
  1. Ops will create the action automatically. For this example, the action is called php/hello.
  /home/openserverless.ops/tmp/deploy.pid
  PID 220917
  > Scan:
  >> Action: packages/php/hello.php
  > Deploying:
  >> Package: php
  $ $OPS package update php 
  ok: updated package php
  >>> Action: packages/php/hello.php
  $ $OPS action update php/hello packages/php/hello.php 
  ok: updated action php/hello
  build process exited with code 0
  UPLOAD ASSETS FROM web
  ==================| UPLOAD RESULTS |==================
  | FILES      : 0
  | COMPLETED  : 0
  | ERRORS     : 0
  | SKIPPED    : 0
  | EXEC. TIME : 2.61 ms
  ======================================================
  URL: http://openserverless.localhost:80

Note: To use a specific version of Python runtime, change the kind property --kind php:8.1, or --kind php:8.3 in the hello.py file.

  1. To invoke the action run the following command:
ops action invoke php/hello --param name Marina --result

2.1 - php:v8.0

php:v8.0 (default: false)

LibraryVersionLink
guzzlehttp/guzzle7.2.0packagist
ramsey/uuid4.1.1packagist
aws/aws-sdk-php3.209.31 || 3.210.0packagist
bcmathLatest*PHP.net
gdLatest*PHP.net
intlLatest*PHP.net
mysqliLatest*PHP.net
mongodbLatest*PHP.net
opcacheLatest*PHP.net
pgsqlLatest*PHP.net
pdo_mysqlLatest*PHP.net
pdo_pgsqlLatest*PHP.net
redisLatest*PHP.net
soapLatest*PHP.net
zipLatest*PHP.net

*Latest version available for current runtime version

2.2 - php:v8.1

php:v8.1 (default: false)

LibraryVersionLink
guzzlehttp/guzzle7.4.5packagist
ramsey/uuid4.4.0packagist
openai-php/clientv0.8.5packagist
aws/aws-sdk-php3.306.7packagist
bcmathLatest*PHP.net
gdLatest*PHP.net
intlLatest*PHP.net
mysqliLatest*PHP.net
mongodbLatest*PHP.net
opcacheLatest*PHP.net
pgsqlLatest*PHP.net
pdo_mysqlLatest*PHP.net
pdo_pgsqlLatest*PHP.net
redisLatest*PHP.net
soapLatest*PHP.net
zipLatest*PHP.net

*Latest version available for current runtime version

2.3 - php:v8.2

php:v8.2 (default: false)

LibraryVersionLink
guzzlehttp/guzzle7.7.0packagist
ramsey/uuid4.7.4packagist
openai-php/clientv0.8.5packagist
aws/aws-sdk-php3.306.7packagist
bcmathLatest*PHP.net
gdLatest*PHP.net
intlLatest*PHP.net
mysqliLatest*PHP.net
mongodbLatest*PHP.net
opcacheLatest*PHP.net
pgsqlLatest*PHP.net
pdo_mysqlLatest*PHP.net
pdo_pgsqlLatest*PHP.net
redisLatest*PHP.net
soapLatest*PHP.net
zipLatest*PHP.net

*Latest version available for current runtime version

2.4 - php:v8.3

php:v8.3 (default: true)

LibraryVersionLink
guzzlehttp/guzzle7.8.1packagist
ramsey/uuid4.7.5packagist
openai-php/clientv0.8.5packagist
aws/aws-sdk-php3.306.7packagist
bcmathLatest*PHP.net
gdLatest*PHP.net
intlLatest*PHP.net
mysqliLatest*PHP.net
mongodbLatest*PHP.net
opcacheLatest*PHP.net
pgsqlLatest*PHP.net
pdo_mysqlLatest*PHP.net
pdo_pgsqlLatest*PHP.net
redisLatest*PHP.net
soapLatest*PHP.net
zipLatest*PHP.net

*Latest version available for current runtime version

3 - Python

Creating and invoking Python actions

The process of creating Python actions is similar to that of other actions. The following sections guide you through creating and invoking a single Python action, and demonstrate how to bundle multiple Python files and third party dependencies.

  1. Create a Python_app folder and then create a package directory.Now create a Python file with the following content inside our packages/python. For this example, the file name is hello.py.
#--web true
#--kind python:default

def main(args):
  name = args.get("name", "stranger")
  result = f"Hello {name}!"
  print(result)
  return {"greeting": result}

The Python file might contain additional functions. However, by convention, a function called main must exist to provide the entry point for the action.

Your directory structure should looks like this:

Python_app
└── packages
    └── Python
        └── hello.py
  1. Run the following command to deploy the action
ops ide deploy
  1. Ops will create the action automatically. For this example, the action is called python/hello.
/home/openserverless/.ops/tmp/deploy.pid
PID 278075
> Scan:
>> Action: packages/python/hello.py
> Deploying:
>> Package: python
$ $OPS package update python 
ok: updated package python
>>> Action: packages/python/hello.py
$ $OPS action update python/hello packages/python/hello.py --web true --kind python:default
ok: updated action python/hello
build process exited with code 0
UPLOAD ASSETS FROM web
==================| UPLOAD RESULTS |==================
| FILES      : 0
| COMPLETED  : 0
| ERRORS     : 0
| SKIPPED    : 0
| EXEC. TIME : 2.37 ms
======================================================
URL: http://opstutorial.localhost:80

Note: To use a specific version of Python runtime, change the kind property --kind python:3.11, or --kind python:3.12 in the hello.py file.

  1. To invoke the action run the following command:
ops action invoke python/hello --param name Marina --result

Packaging Python actions in zip files

Sometimes you action would be more complex and probably you prefer to create multiple file in order to organize better your code.

In this example we’re creating a more complex python action.

  1. Create a folder complex_action inside packages/python folder. Then create two python files

main.py

#--web true
#--kind python:default
import utils

def main(args):
  name = args.get("name", "stranger")
  result = utils.concat_string("Nice to meet you,",name)
  return {"greeting": result}

utils.py

def concat_string(first_string: str, second_string: str):
  return f"{first_string} {second_string}"

Note: The filename of the source file containing the entry point (e.g., main) must be __main__.py.

  1. Deploy the action using ide deploy
ops ide deploy
  1. Now you can invoke your action. In this case the action name is python/complex_action
ops action invoke python/complex_action --param name Marina --result

3.1 - python:v3.10

python:v3.10 (default: false)

LibraryVersionLink
beautifulsoup44.10.0PyPI
httplib20.19.1PyPI
kafka_python2.0.2PyPI
python-dateutil2.8.2PyPI
requests2.31.0PyPI
scrapy2.5.0PyPI
simplejson3.17.5PyPI
twisted21.7.0PyPI
netifaces0.11.0PyPI
pyyaml6.0PyPI
redis4.4.2PyPI
boto31.28.25PyPI
psycopg3.1.10PyPI
pymongo4.4.1PyPI
minio7.1.16PyPI
openai1.7.1PyPI
auth0-python4.6.0PyPI
langchain0.1.0PyPI
farm-haystack1.23.0PyPI
nltk3.8.1PyPI
langdetect1.0.9PyPI
ollama0.1.9PyPI
joblib1.4.2PyPI
lightgbm4.5.0PyPI
feedparser6.0.11PyPI
numpy1.26.4PyPI
scikit-learn1.5.2PyPI
bcrypt4.2.1PyPI

3.2 - python:v3.11

python:v3.11 (default: false)

LibraryVersionLink
beautifulsoup44.10.0PyPI
httplib20.19.1PyPI
kafka_python2.0.2PyPI
python-dateutil2.8.2PyPI
requests2.31.0PyPI
scrapy2.5.0PyPI
simplejson3.17.5PyPI
twisted21.7.0PyPI
netifaces0.11.0PyPI
pyyaml6.0PyPI
redis4.4.2PyPI
boto31.28.25PyPI
psycopg3.1.10PyPI
pymongo4.4.1PyPI
minio7.1.16PyPI
openai1.7.1PyPI
auth0-python4.6.0PyPI
langchain0.1.0PyPI
farm-haystack1.23.0PyPI
nltk3.8.1PyPI
langdetect1.0.9PyPI
plotly5.19.0PyPI
ollama0.1.9PyPI
joblib1.4.2PyPI
lightgbm4.5.0PyPI
feedparser6.0.11PyPI
numpy1.26.4PyPI
scikit-learn1.5.2PyPI
bcrypt4.2.1PyPI

3.3 - python:v3.12

python:v3.12 (default: false)

LibraryVersionLink
beautifulsoup44.10.0PyPI
ollama0.4.5PyPI
openai1.59.3PyPI
pymilvus2.5.3PyPI
redis5.2.1PyPI
pillow11.1.0PyPI
nltk3.8.1PyPI
httplib20.19.1PyPI
kafka_python2.0.2PyPI
python-dateutil2.8.2PyPI
requests2.32.2PyPI
scrapy2.5.0PyPI
simplejson3.17.5PyPI
twisted21.7.0PyPI
netifaces0.11.0PyPI
pyyaml6.0.2PyPI
boto31.35.98PyPI
psycopg3.1.10PyPI
pymongo4.4.1PyPI
minio7.1.16PyPI
auth0-python4.6.0PyPI
langdetect1.0.9PyPI
plotly5.19.0PyPI
joblib1.4.2PyPI
lightgbm4.5.0PyPI
feedparser6.0.11PyPI
numpy1.26.4PyPI
scikit-learn1.5.2PyPI
langchain0.3.14PyPI
langchain-ollama0.2.2PyPI
langchain-openai0.2.14PyPI
langchain-anthropic0.3.1PyPI
langchain-together0.2.0PyPI
langchain-postgres0.0.12PyPI
langchain-milvus0.1.7PyPI
bcrypt4.2.1PyPI
chevron0.14.0PyPI
chess1.11.1PyPI