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.
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:
// Prerequisites
GCC
GCC compiler for local testing (optional)
$ 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
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.
/* * 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 $ gcc -o example example.c # Test with a mock argument $ ./example '{"name":"evrtng"}' { "msg": "Hello from ANSI C!", "args": "{\"name\":\"evrtng\"}" }
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.
# 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.
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 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
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.
# 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
Invoke & Monitor
Test your C function via the wsk CLI or via HTTP. Check logs to see stderr output from your C program.
# 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
# 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"]
$ 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
# 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.
