Using Fn Server to Develop Functions Locally

Find out how to develop functions in a local environment by using the Fn Server component in Fn Project.

OCI Functions is powered by the Fn Project open source engine. The Fn Server is a core component of Fn Project and is responsible for managing functions, handling HTTP requests, and running function containers. You can run the Fn Server locally to provide an environment where you can deploy, invoke, and manage serverless functions. Running Fn Server locally enables you to test and troubleshoot function code before deploying it to Oracle Cloud Infrastructure, streamlining the development workflow while reducing round-trip times for code changes.

Although developing functions locally accelerates development, be aware of the following limitations:

  • Infrastructure Validation: You cannot fully validate OCI-specific infrastructure locally. Networking, security policies, and some infrastructure features can only be tested after deploying to OCI.

  • Resource Principal Authentication: You cannot test resource principal authentication mechanisms locally. Resource principals only work when functions run on OCI infrastructure. Instead, use a user principal (API key) to call OCI services (see Example: Using User Principals (API Keys) Locally), or stub or mock any code that calls OCI services.

The Fn Server provides additional options, such as starting Fn Server with a syslog container for centralized log collection. For more information about the Fn Server, see:

The examples in this topic describe:

Example: Developing and Testing Functions Locally

In this example, you create a "Hello World" Python function, build and deploy the function locally, and then invoke the function.

  1. Log in to your local environment.
  2. Install and start Docker by following the instructions in 1. Install and start Docker in the Functions QuickStart on Local Host.

    A local container engine is required to build and run functions locally. If your environment uses a Docker-compatible alternative (such as Podman Desktop or Rancher Desktop), you can use that instead.

  3. Install the Fn Project CLI by following the instructions in 3. Install Fn Project CLI in the Functions QuickStart on Local Host.
  4. In a terminal window, start the Fn Server locally by entering:
    fn start

    The command starts the Fn Server in a container on your system. You see a message indicating the Fn Server is listening on localhost:8080

  5. Initialize a new Python function named myfunc by entering:
    fn init --runtime python myfunc

    The command creates the myfunc directory with sample code and configuration files. The content of the myfunc directory is as follows:

    
    myfunc/
    ├── func.py
    ├── func.yaml
    └── requirements.txt
  6. Change directory to the newly created function directory ( myfunc ) by entering:

    cd myfunc
  7. Create an application named myapp to group functions by entering:
    fn create app myapp
  8. In the myfunc directory, build and deploy the function locally by entering:
    fn deploy --app myapp --local
  9. Invoke the function using the Fn CLI by entering:
    echo -n '{"name":"World"}' | fn invoke myapp myfunc

    The function runs in a container and returns the following response:

    {"message": "Hello World"}
  10. (Optional) To help troubleshoot issues, restart the Fn Server with debug logging enabled by entering:
    fn start --log-level DEBUG

    Detailed log messages are shown directly in the terminal, including logs from the function code. For example:

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

Example: Using User Principals (API Keys) Locally

This example extends Example: Developing and Testing Functions Locally.

In this example, you use a user API signing key to enable a Python function to list storage buckets in a compartment.

  1. Follow the instructions in Example: Developing and Testing Functions Locally to create, build, deploy, and invoke a Python function named myfunc in an application named myapp.
  2. Create an OCI user, create an OCI group, and assign the user to the group (see 1. Create groups and users in the Functions QuickStart on Local Host).
  3. Create a policy to enable the group to read storage buckets in a particular compartment, in the format:
    Allow group <group-name> to read buckets in compartment <compartment-name>

    See 4. Create policy for group and service in the Functions QuickStart on Local Host.

  4. Set up an API signing key and OCI profile for the OCI user by following the instructions in 2. Set up API signing key and OCI profile in the Functions QuickStart on Local Host to create an ~/.oci/config file and a private key file (a .pem file).
  5. Make sure the ~/.oci/config file contains a DEFAULT section with user, fingerprint, tenancy, region, and key_file fields, in the following format:
    [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 in which the oci buckets reside>
    key_file=/myfunc/.oci/oci_api_key_private_key.pem
  6. Change directory to the myfunc directory by entering:

    cd myfunc
  7. Create a new directory named .oci in the myfunc directory by entering:
    mkdir .oci
  8. Copy the ~/.oci/config file and the ~/.oci/<private-key-file-name>.pem file into the .oci directory. The content of the myfunc directory is as follows:
    
    myfunc/
    ├── .oci/
    │   ├── config
    │   └── oci_api_key_private_key.pem
    ├── func.py
    ├── func.yaml
    └── requirements.txt
  9. Edit the func.py file in your preferred development environment, and replace the contents of the func.py file with the following code to list the buckets in the compartment:
    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"},
            )
  10. In the myfunc directory, build and deploy the function locally by entering:
    fn deploy --app myapp --local
  11. Invoke the function using the Fn CLI by entering:
    fn invoke myapp myfunc

    The function runs in a container and returns a response similar to the following:

    {
        "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
            }
        ]
    }