Most OCI Functions customers get started by following the OCI Functions Quick Start Guide. That’s great for simple use cases, but as your functions get more complex, the edit–deploy–run–verify cycles gets slower. When each change requires a cloud deployment, iteration can become slow and significantly reduce developer productivity compared with local development.

Fn Server helps to address this pain point. It lets you build, run, and test functions on your own machine with minimal setup. In this guide, you’ll learn how to get started in just a few steps.

Prerequisites

Install your favorite container engine desktop

Install Fn Command Line Tool (Fn CLI)

  • To start and manage Fn Server, you’ll need the Fn CLI. Follow the Fn CLI Installation guide to set it up.

Develop your Functions locally

Step 1: Start Fn Server Locally

Run the following command. The Fn CLI pulls the Fn Server image and starts a container on your machine. Fn Server manages function metadata and handles the lifecycle of your function containers.

Command

fn start

You should see Fn Server listening on localhost:8080.

Console

time="2026-02-12T08:59:39Z" level=info msg="Fn serving on `:8080`" type=full version=0.3.768

Step 2: Create a New Function

Use  `fn init` to create a function project. The `--runtime` parameter is used to specify the programming language of the Functions. Here is the list of supported languages. In this example, we are going to use Python function.

Example

fn init --runtime python myfunc

This command creates a new `myfunc` directory containing sample function code and configuration files.

Step 3: Develop Your Function

Navigate into the “myfunc” folder and edit the function code (for example, func.py for Python) using your preferred IDE.

func.py

import io

import json

import logging

from fdk import response

def handler(ctx, data: io.BytesIO = None):

    name = "World"

    try:

        body = json.loads(data.getvalue())

        name = body.get("name")

    except (Exception, ValueError) as ex:

        logging.getLogger().info('error parsing json payload: ' + str(ex))

    logging.getLogger().info("Inside Python Hello World function")

    return response.Response(

        ctx, response_data=json.dumps(

            {"message": "Hello {0}".format(name)}),

        headers={"Content-Type": "application/json"}

    )

Step 4: Create a local application

Create an application if you do not have one yet. An application is a logical grouping of functions. See OCI Functions Concepts for more details.

Command

fn create app myapp

Step 5: Deploy the Function to Local Fn Server

Make sure you are in the Functions folder, which is “myfunc” in this example. The following command builds the function image and deploy it locally to the Fn Server.

Command

fn deploy --app myapp --local

Step 6: Call the Function via Fn CLI

Now you can invoke the function. Fn Server receives the request, starts the function container from your function image. Then the function container processes the request and returns the response.

Command

echo -n '{"name":"World"}' | fn invoke myapp myfunc

Response

{"message": "Hello World"}

Optional: Start Fn Server with DEBUG log level

You could also start Fn Server with DEBUG log level so that you could see function logs in the Fn Server terminal.

Command

fn start --log-level DEBUG

Here is the sample log from Fn Server when debug mode is enabled. In this case, you can see log message outputted from your function code.

Fn Server Log

time="2026-02-12T09:05:14Z" level=debug msg="01KH8HHVDV19C00TGZJ0000001 - root - INFO - Inside Python Hello World function\n" action="server.(*Server).handleFnInvokeCall-fm" app_id=01KH1CPZPG19C00NRZJ0000001 call_id=01KH8HHVDV19C00TGZJ0000001 fn_id=01KH8HAXYD19C00S8ZJ0000004 image="simple-default-python:0.0.140" user_log=true

Limitations

While developing Functions code locally is useful, it comes with the following limitations.

  • OCI infrastructure validation: You cannot fully validate OCI-specific infrastructure setup locally.
  • For example, issues involving networking, security rules, policies, or permissions typically only surface after deploying to OCI.
  • Resource Principals: You cannot test these authentication mechanisms locally.
    • Those security principals only work when you have your Functions running on OCI platform. You may need to either fallback to use User Principal (API Key), or stub/mock the parts of your code that call OCI services.
    • In the next section, we show how you could use User Principal for local Functions development

Using User Principals when developing Functions locally

Since you cannot use Resource Principals when you run your function locally, you have to switch to use User Principal (API Key). Here we will show you how.

Let’s start with an example of a Python function that returns a list of buckets in an OCI compartment.

Step 1: Setup API signing key and OCI profile

Please refer to Functions QuickStart on Local Host, Step C.2 on how to setup API signing key and OCI Profile

Step 2: Group Setup

Please add the user to a group. The group will be used in the policy to access Object Storage. Please refer to Manage Users for more details.

Step 3: Policy Setup

Here we need to setup a policy to allow your user to have access to buckets resources under a compartment. Here is an example of the policy:

OCI Policy

Allow group <group-name> to read buckets in compartment <compartment-name>

More Policy Template could be found here: https://docs.oracle.com/en-us/iaas/Content/Identity/policiescommon/commonpolicies.htm

Please also consult your administrators to make sure the policy is properly reviewed.

Step 4: Copy the <home directory>/.oci/config and the private key of the OCI API key to your function directory

In this example, the filename of the private key is “oci_api_key_private_key.pem

.
├── .oci

│   ├── config

│   └── oci_api_key_private_key.pem

├── func.py

├── func.yaml

└── requirements.txt

Your config file should contain the “DEFAULT” section which is the OCI profile that you are using.

Example of the .oci/config file

[DEFAULT]

user=<the OCID of the user for whom the key pair is being added>

fingerprint=<the fingerprint of the key that was just added>

tenancy=<your tenancy's OCID>

region=<the region where you created the oci bucket>

key_file=/function/.oci/oci_api_key_private_key.pem

Step 5: Deploy and invoke the function code

Here is the example of the code which list buckets in a compartment.

function code

import io

import json

import logging

from fdk import response

import oci

def _load_oci_config():

    """

    Loads OCI config for User API Key auth.

      1) Use OCI config file under home directory

      2) Use DEFAULT oci profile.

    """

    config_file = "/function/.oci/config"

    profile = "DEFAULT"

    return oci.config.from_file(file_location=config_file, profile_name=profile)

def handler(ctx, data: io.BytesIO = None):

    try:

        config = _load_oci_config()

        object_storage = oci.object_storage.ObjectStorageClient(config)

        namespace = "your bucket namespace"

        compartment_id = "your compartment id"

        buckets = object_storage.list_buckets(

            namespace_name=namespace,

            compartment_id=compartment_id,

        ).data

        result = [{

            "name": b.name,

            "createdTime": b.time_created.isoformat() if b.time_created else None,

            "storageTier": getattr(b, "storage_tier", None),

            "publicAccessType": getattr(b, "public_access_type", None),

        } for b in buckets]

        return response.Response(

            ctx,

            response_data=json.dumps({

                "namespace": namespace,

                "compartmentId": compartment_id,

                "count": len(result),

                "buckets": result

            }),

            headers={"Content-Type": "application/json"},

        )

    except Exception as e:

        logging.exception("An exception")

        return response.Response(

            ctx,

            status_code=500,

            response_data=json.dumps({"error": "An exception", "message": str(e)}),

            headers={"Content-Type": "application/json"},

        )

Sample output from the invocation:

functions response

{

    "namespace": "your bucket namespace",

    "compartmentId": "your compartment id",

    "count": 2,

    "buckets": [

        {

            "name": "bucket1",

            "createdTime": "2026-02-25T10:05:39.399000+00:00",

            "storageTier": null,

            "publicAccessType": null

        },

        {

            "name": "bucket2",

            "createdTime": "2026-02-25T10:05:44.798000+00:00",

            "storageTier": null,

            "publicAccessType": null

        }

    ]

}

Additional Resources

Fn Server provides additional options—for example, starting Fn Server with a syslog container for centralized log collection. See the following resources for more details:

Fn Server tutorial: https://fnproject.io/tutorials/

Fn Project documentation: https://github.com/fnproject/docs#-project-documentation

Conclusion

Developing functions locally with Fn Server offers several key benefits: you can rapidly iterate and test changes in real time, avoid any cloud-related costs during development, and receive immediate feedback to speed up debugging and refinement. This streamlined local workflow helps you build reliable serverless functions efficiently. Once your function is ready, you can confidently deploy it to the cloud or production environments. For more details, check out the official Fn Project documentation or explore how to deploy your functions on Oracle Cloud Functions for enterprise use.

Future Plan

OCI Functions is committed to improving the developer experience, including areas such as local debugging and hot reload. Stay tuned for future announcements.