ANSI C in OpenWhisk

Run native C code as a serverless function using a custom Docker runtime on evrtng functions. Maximum performance, zero servers to manage.

ANSI C / GCC Docker Runtime Apache OpenWhisk

Why ANSI C?

OpenWhisk does not ship an ANSI C runtime out of the box โ€” but its Docker action support means you can run any compiled C binary as a serverless function. This approach gives you:

Near-native performance Compiled C executes directly โ€” no interpreter overhead.
Tiny container image A statically linked C binary can produce a container image under 10 MB.
Full C standard library GCC, libc, and any library you need โ€” full control via Dockerfile.
Native OpenWhisk integration Your C function receives JSON params, returns JSON โ€” same as any other action.

// Prerequisites

wsk CLI OpenWhisk CLI installed and configured docs โ†’
Docker Docker Desktop or Docker Engine running locally docs โ†’
GCC GCC compiler for local testing (optional)
Registry Docker Hub account or private container registry docs โ†’
Auth Key evrtng functions API key from your account docs โ†’
check prerequisites
$ wsk --version
wsk 1.2.0

$ docker --version
Docker version 25.0.0

$ gcc --version
gcc (Ubuntu 11.4.0) 11.4.0

Step-by-Step Guide

01

Write Your C Function

OpenWhisk communicates with your function via stdin/stdout using a simple JSON protocol. Your C program reads a JSON string from stdin and writes a JSON string to stdout.

example.c
/*
 * evrtng functions โ€“ ANSI C action example
 * Reads JSON from argv[1] (OpenWhisk passes params as arg),
 * prints a JSON result to stdout.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {

    /* Log to stderr โ€“ shows up in wsk activation logs */
    fprintf(stderr,
        "[evrtng] ANSI C action invoked\n");

    /* Safely read the name param from argv[1] */
    const char *args = (argc > 1) ? argv[1] : "undefined";

    /* Print structured JSON result to stdout */
    printf(
        "{ \"msg\": \"Hello from ANSI C!\", \"args\": \"%s\" }\n",
        args
    );

    return 0;
}
compile & test locally
# Compile
$ gcc -o example example.c

# Test with a mock argument
$ ./example '{"name":"evrtng"}'
{ "msg": "Hello from ANSI C!", "args": "{\"name\":\"evrtng\"}" }
02

Create the Dockerfile

Package the compiled binary in a minimal Docker image. We compile inside the container to ensure the binary matches the runtime architecture.

Dockerfile
# Stage 1 โ€“ Build
FROM ubuntu:22.04 AS builder

RUN apt-get update && \
    apt-get install -y gcc libc-dev && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /build
COPY example.c .

# Compile with optimisations and static linking
RUN gcc -O2 -static -o example example.c

# Stage 2 โ€“ Runtime (minimal scratch image)
FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y libc6 && \
    rm -rf /var/lib/apt/lists/*

COPY --from=builder /build/example /usr/local/bin/example

# OpenWhisk calls this entrypoint
ENTRYPOINT ["/usr/local/bin/example"]

The multi-stage build keeps the final image small. Using -static for the C binary makes it fully self-contained even on a minimal base image.

03

Build & Push the Docker Image

Build the image locally and push it to a registry that evrtng functions can access. Replace YOUR_DOCKERHUB_USER with your Docker Hub username.

build & push
# Build the image
$ docker build -t YOUR_DOCKERHUB_USER/openwhisk-ansi-c .

Step 1/8 : FROM ubuntu:22.04 AS builder
...
Successfully built a1b2c3d4e5f6
Successfully tagged yourusername/openwhisk-ansi-c:latest

# Log in to Docker Hub
$ docker login

# Push the image
$ docker push YOUR_DOCKERHUB_USER/openwhisk-ansi-c

The push refers to repository [docker.io/yourusername/openwhisk-ansi-c]
latest: digest: sha256:abc123... size: 27.4 MB

# Verify the image is accessible
$ docker pull YOUR_DOCKERHUB_USER/openwhisk-ansi-c
04

Register the Action in OpenWhisk

Create the action using the --docker flag, pointing to your pushed image. The --web true flag makes it accessible via HTTP.

wsk action create
# Create the action with your Docker image
$ wsk action create ansiCAction \
    --docker YOUR_DOCKERHUB_USER/openwhisk-ansi-c \
    --web true

ok: created action ansiCAction

# Verify it was registered
$ wsk action list | grep ansiCAction
/guest/default/ansiCAction    private  blackbox

# Get the public web URL
$ wsk action get ansiCAction --url
https://fnc.evrtng.cloud/api/v1/web/guest/default/ansiCAction
05

Invoke & Monitor

Test your C function via the wsk CLI or via HTTP. Check logs to see stderr output from your C program.

invoke via CLI
# Blocking invocation with params
$ wsk action invoke ansiCAction \
    --result \
    --param args "test-input"

{
    "msg": "Hello from ANSI C!",
    "args": "test-input"
}

# Async invocation
$ wsk action invoke ansiCAction --param args "async-test"
ok: invoked /guest/default/ansiCAction with id a1b2c3d4...

# View the logs (stderr output from your C program)
$ wsk activation logs a1b2c3d4
2025-01-01T12:00:00.000Z stderr: [evrtng] ANSI C action invoked
invoke via HTTP (Web Action)
# POST via curl
$ curl -X POST \
  https://fnc.evrtng.cloud/api/v1/web/guest/default/ansiCAction \
  -H "Content-Type: application/json" \
  -d '{"args": "curl-test"}'

{
    "msg": "Hello from ANSI C!",
    "args": "curl-test"
}

# GET with query param
$ curl "https://fnc.evrtng.cloud/api/v1/web/guest/default/ansiCAction?args=hello"
{ "msg": "Hello from ANSI C!", "args": "hello" }

Parsing JSON with cJSON

For real-world use you will want to parse the JSON params OpenWhisk passes. Here is a more complete example using the lightweight cJSON library.

action_json.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"   /* cJSON: MIT License */

int main(int argc, char *argv[]) {

    /* OpenWhisk passes JSON as first CLI argument */
    if (argc < 2) {
        printf("{\"error\":\"no params\"}\n");
        return 1;
    }

    /* Parse input JSON */
    cJSON *input = cJSON_Parse(argv[1]);
    if (!input) {
        printf("{\"error\":\"invalid JSON\"}\n");
        return 1;
    }

    /* Extract "name" field */
    cJSON *name_item = cJSON_GetObjectItem(input, "name");
    const char *name = (name_item && cJSON_IsString(name_item))
        ? name_item->valuestring
        : "World";

    /* Build and output the response */
    cJSON *output = cJSON_CreateObject();
    cJSON_AddStringToObject(output, "message",
        strcat(strcpy(malloc(64), "Hello, "), name));
    cJSON_AddStringToObject(output, "runtime", "ANSI C");

    printf("%s\n", cJSON_Print(output));

    cJSON_Delete(input);
    cJSON_Delete(output);
    return 0;
}

Dockerfile with cJSON

FROM ubuntu:22.04 AS builder

RUN apt-get update && apt-get install -y \
    gcc libc-dev wget && \
    rm -rf /var/lib/apt/lists/*

# Download cJSON (MIT license)
RUN wget -q \
  https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.c \
  https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.h

WORKDIR /build
COPY action_json.c cJSON.c cJSON.h .

# Compile with cJSON
RUN gcc -O2 -o action action_json.c cJSON.c -lm

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y libc6 && \
    rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/action /usr/local/bin/action
ENTRYPOINT ["/usr/local/bin/action"]
deploy & test
$ docker build -t YOU/ow-c-json .
$ docker push YOU/ow-c-json

$ wsk action create cJsonAction \
    --docker YOU/ow-c-json \
    --web true

$ wsk action invoke cJsonAction \
    --result \
    --param name "evrtng"

{
  "message": "Hello, evrtng",
  "runtime": "ANSI C"
}

Managing Your C Action

wsk cheat sheet โ€“ C actions
# Update after rebuilding the Docker image
$ wsk action update ansiCAction \
    --docker YOU/openwhisk-ansi-c

# Set memory limit (e.g. 256 MB for C processing)
$ wsk action update ansiCAction \
    --docker YOU/openwhisk-ansi-c \
    --memory 256

# Set a timeout (e.g. 30 seconds)
$ wsk action update ansiCAction \
    --docker YOU/openwhisk-ansi-c \
    --timeout 30000

# View action details
$ wsk action get ansiCAction

# Use in a sequence with other actions
$ wsk action create myPipeline \
    --sequence ansiCAction,anotherAction

# Delete the action
$ wsk action delete ansiCAction
ok: deleted action ansiCAction

Implementation Notes

01 OpenWhisk passes parameters as a JSON string in argv[1], not via stdin. Your C program must read argv[1].
02 Output must be valid JSON on stdout. Anything on stderr appears in the activation logs (use for debugging).
03 The Docker image must be publicly accessible, or hosted in a registry configured with evrtng functions.
04 For security, compile with -fstack-protector-strong and validate all inputs before use.
05 If you need shared libraries (openssl, curl, etc.) link them statically or install them in the Docker image.
06 cJSON (MIT license) is a safe choice for JSON handling in C. libjansson and yyjson are also good alternatives.

Next Steps