OCI Functions: Serverless Guide
Build serverless applications with OCI Functions: Fn Project, Docker-based functions, API Gateway integration, and event triggers.
Prerequisites
- Basic understanding of serverless concepts
- Docker and container basics
Introduction to OCI Functions
OCI Functions is Oracle's serverless compute platform built on the open-source Fn Project. It lets you write and deploy code that runs in response to events or HTTP requests without provisioning, managing, or scaling any infrastructure. You package your code as a Docker container, deploy it to OCI Functions, and the platform handles everything else: allocating compute resources, scaling up on demand, scaling to zero when idle, and managing the execution lifecycle.
Unlike some serverless platforms that restrict you to specific runtimes and packaging formats, OCI Functions runs any Docker container. This means you can use any programming language, any library, any binary, and any base image. The Fn Project provides first-class Function Development Kits (FDKs) for Python, Java, Go, Node.js, Ruby, and C#, but you are not limited to these languages. If it runs in a container, it runs on OCI Functions.
This guide covers the entire OCI Functions workflow: setting up the Fn CLI and OCI integration, creating function applications and functions, building and deploying containers, configuring triggers from OCI Events, API Gateway, and other services, managing configuration and secrets, monitoring and troubleshooting, and optimizing cold start performance.
OCI Functions Pricing
OCI Functions charges based on two dimensions: the number of function invocations and the compute time measured in GB-seconds (memory allocated multiplied by execution duration). The Always Free tier includes 2 million function invocations and 400,000 GB-seconds per month. Beyond that, invocations cost $0.20 per million and compute costs $0.00001417 per GB-second. There is no charge when functions are idle (scaled to zero). Inbound network traffic is free; outbound traffic is charged at standard OCI data transfer rates.
Setting Up the Development Environment
OCI Functions development requires the Fn CLI for local development and testing, Docker for building container images, and the OCI CLI for deployment configuration. The Fn CLI provides commands for creating, building, testing, and deploying functions, while Docker handles the container image lifecycle.
# Install the Fn CLI
# macOS
brew install fn
# Linux
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
# Verify installation
fn version
# Configure Fn CLI to use OCI as the provider
fn create context oci-production --provider oracle
fn use context oci-production
# Set the OCI configuration
fn update context oracle.compartment-id <compartment-ocid>
fn update context oracle.profile DEFAULT
fn update context api-url https://functions.<region>.oci.oraclecloud.com
fn update context registry <region-key>.ocir.io/<tenancy-namespace>
# Log in to OCI Container Registry
docker login <region-key>.ocir.io \
-u '<tenancy-namespace>/oracleidentitycloudservice/<email>' \
-p '<auth-token>'
# Create IAM policies for Functions
oci iam policy create \
--compartment-id <tenancy-ocid> \
--name FunctionsPolicy \
--description "Policies for OCI Functions" \
--statements '[
"Allow group Developers to manage functions-family in compartment serverless",
"Allow group Developers to use virtual-network-family in compartment serverless",
"Allow group Developers to manage repos in compartment serverless",
"Allow group Developers to read metrics in compartment serverless",
"Allow service faas to use virtual-network-family in compartment serverless",
"Allow service faas to read repos in tenancy"
]'Creating Your First Function
Functions are organized into applications. An application is a logical grouping of related functions that share the same subnet, configuration variables, and logging settings. Think of an application as a microservice: all functions within it share the same network context and can access the same resources.
# Create a function application
oci fn application create \
--compartment-id <compartment-ocid> \
--display-name "ecommerce-functions" \
--subnet-ids '["<subnet-ocid>"]' \
--config '{"DATABASE_URL": "jdbc:oracle:thin:@adb_high", "LOG_LEVEL": "info"}'
# Initialize a new Python function
fn init --runtime python order-processor
cd order-processor
# View the generated project structure
ls -la
# func.py - Your function code
# func.yaml - Function metadata and configuration
# requirements.txt - Python dependenciesWriting Function Code
# func.py - Order processing function
import io
import json
import logging
import oci
from fdk import response
logger = logging.getLogger()
def handler(ctx, data: io.BytesIO):
"""
Process incoming order events.
Triggered by OCI Events when a message arrives in the orders stream,
or directly via API Gateway.
"""
try:
body = json.loads(data.getvalue())
logger.info(f"Processing order: {body.get('orderId', 'unknown')}")
# Get configuration from the function application
cfg = ctx.Config()
db_url = cfg.get("DATABASE_URL", "")
log_level = cfg.get("LOG_LEVEL", "info")
# Use resource principal authentication (auto-configured in Functions)
signer = oci.auth.signers.get_resource_principals_signer()
# Example: Write order to Object Storage
object_storage = oci.object_storage.ObjectStorageClient(
config={}, signer=signer
)
namespace = object_storage.get_namespace().data
order_data = json.dumps(body, indent=2).encode()
order_id = body.get("orderId", "unknown")
object_storage.put_object(
namespace_name=namespace,
bucket_name="processed-orders",
object_name=f"orders/{order_id}.json",
put_object_body=order_data,
content_type="application/json"
)
# Example: Send notification
ons_client = oci.ons.NotificationDataPlaneClient(
config={}, signer=signer
)
ons_client.publish_message(
topic_id=cfg.get("NOTIFICATION_TOPIC_OCID", ""),
message_details=oci.ons.models.MessageDetails(
title=f"Order {order_id} Processed",
body=f"Order total: ${body.get('total', 0):.2f}"
)
)
result = {
"status": "processed",
"orderId": order_id,
"storagePath": f"orders/{order_id}.json"
}
return response.Response(
ctx,
response_data=json.dumps(result),
headers={"Content-Type": "application/json"}
)
except Exception as e:
logger.error(f"Error processing order: {str(e)}")
return response.Response(
ctx,
response_data=json.dumps({"error": str(e)}),
headers={"Content-Type": "application/json"},
status_code=500
)# func.yaml - Function configuration
schema_version: 20180708
name: order-processor
version: 0.0.1
runtime: python
build_image: fnproject/python:3.11-dev
run_image: fnproject/python:3.11
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
timeout: 120
config:
DATABASE_URL: "jdbc:oracle:thin:@adb_high"
LOG_LEVEL: "info"Building and Deploying Functions
Functions are deployed as Docker container images to the OCI Container Registry (OCIR). The Fn CLI automates the build and push process using the Dockerfile generated from your func.yaml configuration. You can also bring your own Dockerfile for complete control over the build process.
# Build and deploy the function to OCI
fn deploy --app ecommerce-functions
# This command:
# 1. Builds the Docker image using func.yaml settings
# 2. Pushes the image to OCIR
# 3. Creates or updates the function in OCI
# 4. Sets the function configuration
# Deploy with verbose output for debugging
fn deploy --app ecommerce-functions --verbose
# Test the function locally before deploying
fn start # Start the local Fn server
echo '{"orderId": "ORD-001", "total": 99.99}' | fn invoke ecommerce-functions order-processor
# Invoke the deployed function via CLI
echo '{"orderId": "ORD-002", "total": 149.99}' | fn invoke ecommerce-functions order-processor
# Invoke via OCI CLI
oci fn function invoke \
--function-id <function-ocid> \
--body '{"orderId": "ORD-003", "total": 299.99}' \
--file "-"
# List functions in an application
oci fn function list \
--application-id <app-ocid> \
--query 'data[].{"Name": "display-name", "Memory (MB)": "memory-in-mbs", "Timeout (s)": "timeout-in-seconds", "Image": image}' \
--output table
# Update function configuration
oci fn function update \
--function-id <function-ocid> \
--memory-in-mbs 512 \
--timeout-in-seconds 300 \
--config '{"DATABASE_URL": "new-connection-string", "LOG_LEVEL": "debug"}'Cold Start Optimization
The first invocation after a period of inactivity (cold start) takes longer because OCI must provision a container and load your function. Typical cold starts range from 1-5 seconds for Python/Node.js and 5-15 seconds for Java. To minimize cold starts: keep your function image small (use slim base images), minimize dependencies, use provisioned concurrency for latency-sensitive functions, initialize SDK clients outside the handler function (module-level), and avoid large imports that are not needed for every invocation.
Function Triggers and Integration
OCI Functions can be triggered by multiple sources: OCI Events for resource change notifications, OCI API Gateway for HTTP requests, OCI Streaming for message processing, OCI Notifications for pub/sub events, and direct invocation via the OCI SDK or CLI. Each trigger type passes different event data to your function handler.
API Gateway Integration
# Create an API Gateway
oci api-gateway gateway create \
--compartment-id <compartment-ocid> \
--display-name "ecommerce-api-gw" \
--endpoint-type PUBLIC \
--subnet-id <public-subnet-ocid>
# Create a deployment with routes to functions
oci api-gateway deployment create \
--compartment-id <compartment-ocid> \
--gateway-id <gateway-ocid> \
--display-name "ecommerce-api" \
--path-prefix "/api/v1" \
--specification '{
"routes": [
{
"path": "/orders",
"methods": ["POST"],
"backend": {
"type": "ORACLE_FUNCTIONS_BACKEND",
"functionId": "<order-processor-function-ocid>"
},
"requestPolicies": {
"bodyValidation": {
"required": true,
"content": {
"application/json": {
"validationType": "NONE"
}
},
"validationMode": "ENFORCING"
}
}
},
{
"path": "/orders/{orderId}",
"methods": ["GET"],
"backend": {
"type": "ORACLE_FUNCTIONS_BACKEND",
"functionId": "<get-order-function-ocid>"
}
},
{
"path": "/health",
"methods": ["GET"],
"backend": {
"type": "STOCK_RESPONSE_BACKEND",
"status": 200,
"body": "{\"status\": \"healthy\"}",
"headers": [
{"name": "Content-Type", "value": "application/json"}
]
}
}
]
}'
# Get the API Gateway endpoint URL
oci api-gateway deployment get \
--deployment-id <deployment-ocid> \
--query 'data.endpoint'Event-Triggered Functions
# Trigger function on Object Storage events
oci events rule create \
--compartment-id <compartment-ocid> \
--display-name "process-uploaded-files" \
--is-enabled true \
--condition '{
"eventType": [
"com.oraclecloud.objectstorage.createobject",
"com.oraclecloud.objectstorage.updateobject"
],
"data": {
"additionalDetails": {
"bucketName": ["uploads"]
}
}
}' \
--actions '{
"actions": [{
"actionType": "FAAS",
"functionId": "<file-processor-function-ocid>",
"isEnabled": true
}]
}'
# Trigger function on compute instance state changes
oci events rule create \
--compartment-id <compartment-ocid> \
--display-name "instance-lifecycle-handler" \
--is-enabled true \
--condition '{
"eventType": [
"com.oraclecloud.computeapi.launchinstance.end",
"com.oraclecloud.computeapi.terminateinstance.end"
]
}' \
--actions '{
"actions": [{
"actionType": "FAAS",
"functionId": "<lifecycle-function-ocid>",
"isEnabled": true
}]
}'Configuration and Secrets Management
Functions receive configuration through application-level and function-level config variables, accessible via ctx.Config() in the handler. Application-level variables apply to all functions in the application, while function-level variables override application-level values for specific functions. For sensitive values like database passwords and API keys, use OCI Vault to store secrets and retrieve them using the OCI SDK with resource principal authentication.
# Retrieving secrets from OCI Vault inside a function
import oci
import base64
import json
import functools
# Cache secrets to avoid Vault calls on every invocation
@functools.lru_cache(maxsize=32)
def get_secret(secret_ocid):
"""Retrieve and cache a secret from OCI Vault."""
signer = oci.auth.signers.get_resource_principals_signer()
secrets_client = oci.secrets.SecretsClient(config={}, signer=signer)
response = secrets_client.get_secret_bundle(
secret_id=secret_ocid,
stage="CURRENT"
)
secret_content = response.data.secret_bundle_content
if secret_content.content_type == "BASE64":
return base64.b64decode(secret_content.content).decode()
return secret_content.content
def handler(ctx, data):
cfg = ctx.Config()
# Get database password from Vault
db_password = get_secret(cfg["DB_PASSWORD_SECRET_OCID"])
# Get API key from Vault
api_key = get_secret(cfg["API_KEY_SECRET_OCID"])
# Use the secrets in your function logic
# ... connect to database, call external API, etc.Monitoring and Troubleshooting
OCI Functions integrates with OCI Monitoring for metrics and OCI Logging for function execution logs. Key metrics include invocation count, execution duration, error count, and concurrent executions. Function logs capture stdout/stderr output from your function code, including any logging statements and unhandled exceptions.
# Enable logging for a function application
oci fn application update \
--application-id <app-ocid> \
--syslog-url "tcp://ocid1.log.oc1...<log-ocid>"
# View function invocation metrics
oci monitoring metric-data summarize-metrics-data \
--compartment-id <compartment-ocid> \
--namespace oci_faas \
--query-text 'FunctionInvocationCount[1h]{functionId = "<function-ocid>"}.sum()' \
--start-time "2026-03-14T00:00:00Z" \
--end-time "2026-03-14T23:59:59Z"
# View function execution duration
oci monitoring metric-data summarize-metrics-data \
--compartment-id <compartment-ocid> \
--namespace oci_faas \
--query-text 'FunctionExecutionDuration[1h]{functionId = "<function-ocid>"}.percentile(0.95)' \
--start-time "2026-03-14T00:00:00Z" \
--end-time "2026-03-14T23:59:59Z"
# View function error count
oci monitoring metric-data summarize-metrics-data \
--compartment-id <compartment-ocid> \
--namespace oci_faas \
--query-text 'FunctionResponseCount[5m]{functionId = "<function-ocid>", isError = "true"}.sum()' \
--start-time "2026-03-14T00:00:00Z" \
--end-time "2026-03-14T23:59:59Z"
# Create an alarm for function errors
oci monitoring alarm create \
--compartment-id <compartment-ocid> \
--display-name "Function Error Alert" \
--namespace oci_faas \
--query 'FunctionResponseCount[5m]{functionId = "<function-ocid>", isError = "true"}.sum() > 10' \
--severity CRITICAL \
--is-enabled true \
--destinations '["<notification-topic-ocid>"]' \
--body "Function order-processor has more than 10 errors in 5 minutes"
# View function logs (via OCI Logging)
oci logging search \
--search-query 'search "<log-group-ocid>" | where functionName = "order-processor" | sort by datetime desc | limit 50' \
--time-start "2026-03-14T00:00:00Z" \
--time-end "2026-03-14T23:59:59Z"Debugging Locally
Use the Fn CLI's local development server (fn start) to test functions on your machine before deploying to OCI. Local testing avoids the deploy-invoke-check-logs cycle and provides immediate feedback. For functions that call OCI services, use API key authentication locally (instead of resource principal) by setting the OCI_CONFIG_FILE environment variable. You can also run the container directly with Docker to debug networking, file system, and environment issues.
Best Practices and Patterns
Building reliable serverless applications on OCI Functions requires attention to idempotency, error handling, performance, and cost management.
Function Design Guidelines
| Practice | Why | How |
|---|---|---|
| Keep functions focused | Easier testing, faster cold starts | One function per action (create, process, notify) |
| Make handlers idempotent | Events may deliver duplicates | Use event IDs for deduplication |
| Initialize outside handler | Reuse across warm invocations | Create SDK clients at module level |
| Handle errors gracefully | Prevent silent failures | Log errors, return appropriate status codes |
| Set appropriate timeouts | Prevent runaway costs | Set timeout just above expected max duration |
| Use minimal images | Faster cold starts | Use slim/distroless base images |
| Right-size memory | Balance cost and performance | Profile and test with different memory settings |
For production deployments, implement structured JSON logging so logs can be parsed and queried in Logging Analytics. Use OCI resource principal authentication instead of API keys. Configure dead letter queues (via Streaming) for events that fail processing. Tag function applications with cost tracking labels. Monitor cold start frequency and optimize image size if cold starts are impacting user experience.
OCI Streaming & EventsOCI DevOps CI/CDOCI Container InstancesKey Takeaways
- 1OCI Functions is built on the open-source Fn Project and runs any Docker container.
- 2Functions scale to zero when idle and scale up automatically on demand.
- 3API Gateway provides HTTP endpoints with authentication, rate limiting, and routing to functions.
- 4Resource principal authentication enables secure access to OCI services without managing credentials.
Frequently Asked Questions
What languages does OCI Functions support?
What is the cold start time for OCI Functions?
Written by CloudToolStack Team
Cloud engineers and architects with hands-on experience across AWS, Azure, and GCP. We write guides based on real-world production patterns, not just documentation rewrites.
Disclaimer: This guide is for educational purposes. Cloud services change frequently; always refer to official documentation for the latest information. AWS, Azure, and GCP are trademarks of their respective owners.