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
- Fn Server is compatible with – and tested on – Docker Desktop, Podman Desktop and Rancher Desktop. Choose one and install it by following the instructions on the vendor’s website.
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.
