OCI Functions is Oracle Cloud Infrastructure's functions-as-a-service offering. It is built on the open-source Fn Project, which shapes almost everything about it: functions are ordinary container images, the developer tool is the fn CLI, and the in-container contract is provided by Fn's function development kits (FDKs). If you know serverless from another cloud, most concepts carry over, and our serverless architecture guide covers the general model of triggers, concurrency, retries and dead-letter handling. This page is about what is specific to OCI: how applications, images, subnets and identity fit together, the limits that shape designs, the two invocation modes, and the operational traps.

Limits and flags below were checked against Oracle's documentation on 2026-10-01. Oracle changes this service, notably adding hour-long detached invocations in October 2025, so treat the numbers as a snapshot and re-check the limits page before you design around one.

Advertisement

The model: applications, functions, images

An application is a named grouping of functions in a compartment. It carries the networking (one or more subnets in a VCN where function containers get their network interfaces), shared configuration, logging settings and the IAM scope that policies typically reference. A function is a container image stored in OCI Registry (OCIR) plus metadata: memory, timeout, configuration and the image digest. When a function is invoked, the service pulls the image if needed, starts a container on infrastructure it manages, attaches it to the application's subnet, and passes the request through the FDK to your handler.

Three consequences follow from that design. Your function can reach private resources in the VCN, such as databases and internal APIs, without public exposure, but it also needs a route to anything outside: a NAT gateway for the internet and a service gateway for Oracle services, as described in our OCI networking guide. Any language can be used if you supply an image that speaks the Fn contract, though the official FDKs (Python, Java, Node, Go, Ruby and C#) are the practical path. And deployment is an image push, so the usual container hygiene applies: small base images, pinned dependencies, and scanning in the registry.

API GatewayHTTP routeEvents servicee.g. object createdOCI CLI / SDKsigned invokeInvoke endpointsync or detachedFunction containerimage from OCIRApplication subnetVNIC in your VCNResource principaldynamic group + policyLoggingstdout / stderrOCI servicesObject Storage, VaultDestinationsNotifications / Queue / StreamingruntokenAPI callsdetached resultThe caller only ever talks to the invoke endpoint; everything to the right runs in the tenancy's network and identity context.
Invocation path for OCI Functions: triggers and clients call a single invoke endpoint; the service runs the function's OCIR image as a container attached to the application's subnet, the function authenticates as itself through a resource principal, logs go to OCI Logging and detached results can be delivered to a destination.

Limits that shape the design

SettingValueDesign consequence
Memory128 (default), 256, 512, 1024, 2048, 3072 MBMore memory can cure timeouts; Oracle advises at least 256 MB for Java
Synchronous timeout30 s default, 300 s maximumAnything longer must be detached or split
Detached timeout5 to 3,600 s (defaults to the sync value)Hour-long batch work fits without chaining
Request and response payload6 MB each, fixedPass object references, not data; oversize returns HTTP 413
ConcurrencyScales automatically up to the tenancy's memory limitPlan capacity in memory, not invocation counts

Oracle's guidance is to set the timeout close to what the function actually needs rather than to the maximum, and to raise memory if invocations hit HTTP 504 timeouts. Compute-heavy work such as image decoding often runs faster at a larger size, so measure duration and cost per invocation at two or three sizes before choosing.

Advertisement

Writing a function

Running fn init --runtime python thumbnailer scaffolds a directory with func.py, func.yaml and requirements.txt. The generated func.yaml also pins build and run images for the runtime; keep whatever your CLI version generates rather than copying tags from an old tutorial. The fields you will edit look like this:

schema_version: 20180708
name: thumbnailer
version: 0.0.1
runtime: python
entrypoint: /python/bin/fdk /function/func.py handler
memory: 512        # MB: 128, 256, 512, 1024, 2048 or 3072
timeout: 60        # seconds, synchronous; max 300

The handler receives a context and the request body as a byte stream, and returns an FDK Response. The example below is the worked example for this page: when an image lands in an Object Storage bucket, create a thumbnail next to it.

import io, json, logging
import oci
from fdk import response

# Created once per container, reused across invocations while the container is warm.
_signer = oci.auth.signers.get_resource_principals_signer()
_os = oci.object_storage.ObjectStorageClient(config={}, signer=_signer)
_log = logging.getLogger()

def handler(ctx, data: io.BytesIO = None):
    cfg = ctx.Config()                         # function + application config, as a dict
    event = json.loads(data.getvalue())        # Events service payload
    d = event["data"]
    ns, bucket, name = d["additionalDetails"]["namespace"], d["additionalDetails"]["bucketName"], d["resourceName"]

    if name.startswith(cfg["OUTPUT_PREFIX"]):  # never react to our own output
        return response.Response(ctx, response_data=json.dumps({"skipped": name}))

    src = _os.get_object(ns, bucket, name).data.content
    thumb = make_thumbnail(src, int(cfg.get("MAX_EDGE", "256")))
    out = cfg["OUTPUT_PREFIX"] + name
    _os.put_object(ns, bucket, out, thumb)     # same key every time: retries are idempotent
    _log.info("thumbnail %s -> %s (%d bytes)", name, out, len(thumb))
    return response.Response(ctx, response_data=json.dumps({"written": out}),
                             headers={"Content-Type": "application/json"})

Three habits in that code matter more than the thumbnail logic. Clients are built at module level so warm containers reuse them instead of re-authenticating on every call. The function ignores its own output prefix, otherwise writing the thumbnail would raise a new object-created event and the function would trigger itself in a loop. And the output key is derived from the input, so a retried invocation overwrites the same object instead of creating duplicates; idempotency is the only safe assumption for event-driven code.

Deploying and configuring

# One-time: the application owns the subnet(s) its functions run in.
fn create app media-app --annotation oracle.com/oci/subnetIds='["ocid1.subnet.oc1..exampleuniqueid"]'

# Scaffold, then build, push to OCIR and register in one step.
fn init --runtime python thumbnailer
cd thumbnailer && fn -v deploy --app media-app

# Configuration lives outside the image.
fn config app media-app OUTPUT_PREFIX thumbs/
fn config function media-app thumbnailer MAX_EDGE 256

# Limits can be changed without a rebuild.
fn update function --memory 512 --timeout-in-seconds 60 media-app thumbnailer

# Synchronous and detached invocation through the OCI CLI.
oci fn function invoke --function-id "$FN_OCID" --file - --body '{"ping": 1}'
oci fn function invoke --function-id "$FN_OCID" --file - --body "" --fn-invoke-type "detached"

fn deploy builds the image locally, pushes it to the OCIR repository configured in your Fn context, and creates or updates the function to point at the new digest. Configuration values set with fn config are delivered to the handler through ctx.Config(); application-level keys are shared by every function in the app and function-level keys override them. Do not put credentials there: store secrets in OCI Vault and read them at start-up with the resource principal, caching the value for the life of the container.

For production, drive the same steps from CI with Terraform or the OCI CLI so the function definition (image digest, memory, timeout, config) is reviewed and versioned, and promote by digest between environments rather than rebuilding.

Identity: resource principals, not keys

A function should never carry an API signing key. Instead it authenticates as itself through a resource principal: the service injects a short-lived credential, and the SDK's get_resource_principals_signer() turns it into a request signer. What the function may do is controlled by putting functions into a dynamic group with a matching rule and granting that group narrowly scoped policies. The people or CI pipeline that deploy functions need their own group policies, for example to select the VCN and subnet and to read OCIR repositories. The write grant is narrowed to creating and overwriting objects (retries overwrite), so the function cannot delete what it reads.

# Dynamic group: every function in one compartment.
ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..exampleuniqueid'}

# What the function may do, granted to the dynamic group, scoped tightly.
Allow dynamic-group media-fns to read objects in compartment media where target.bucket.name = 'uploads'
Allow dynamic-group media-fns to manage objects in compartment media where all {target.bucket.name = 'uploads', any {request.permission = 'OBJECT_CREATE', request.permission = 'OBJECT_OVERWRITE'}}
Allow dynamic-group media-fns to read secret-bundles in compartment media

# What the developers or CI pipeline that deploy functions need (from Oracle's policy reference).
Allow group fn-deployers to use virtual-network-family in compartment media
Allow group fn-deployers to read repos in tenancy

Scope the dynamic group rule to one compartment or, better, to specific function OCIDs, and write policies with conditions such as bucket name. A rule that matches every function in the tenancy combined with broad manage grants turns any compromised function into a tenancy-wide foothold. The policy language, compartments and conditions are covered in depth in our OCI IAM guide.

Synchronous versus detached invocation

In synchronous mode the caller holds the connection until the handler returns, up to the synchronous timeout, and receives the response body. This is the mode behind API Gateway routes and direct SDK calls, and it is the right choice when a caller needs the answer.

In detached mode, selected with the fn-invoke-type: detached request header or the CLI's --fn-invoke-type detached flag, the service returns HTTP 202 as soon as processing begins and the caller moves on. Since October 2025 detached invocations have their own timeout of up to 3,600 seconds, and the outcome of successful and failed runs can be delivered to the Notifications, Queue or Streaming services. That turns a function into a reasonable home for hour-scale batch steps, such as a nightly export or a document-conversion job, without splitting the work into chained five-minute pieces.

The trade-off is that detached mode moves responsibility for results to you. Configure a failure destination and consume it, because a detached run that fails with nobody listening is silent data loss. Make the work idempotent, because whoever retries a failure will run it again. And keep the result small: put the output in Object Storage and send a reference, which also keeps you under the 6 MB payload limit.

Triggers and the worked pipeline

Functions are invoked by API Gateway (HTTP routes with authentication and rate limiting in front), the Events service (rules matching resource events), Connector Hub (moving data from logging, streaming or queues into a function), Notifications subscriptions and direct SDK or CLI calls. For the thumbnail pipeline the steps are: enable object events on the uploads bucket, which are off by default; create an Events rule matching com.oraclecloud.objectstorage.createobject for that bucket, with the function as the action; and grant the policies above. Our Object Storage guide covers bucket settings and lifecycle rules that keep the output from growing forever.

Size it with arithmetic, not hope. Suppose uploads arrive at 20 per second at peak and each thumbnail takes 0.8 seconds at 512 MB. By Little's law about 16 invocations run concurrently, which is about 8 GB of function memory in flight. Check that against your tenancy's Functions memory limit before launch, and remember that every other function in the region draws on the same pool.

Cold starts and provisioned concurrency

The first invocation after deployment or after an idle period has to provision infrastructure, pull the image and start the container; Oracle's documentation describes this as taking several seconds or longer. Subsequent calls reuse the warm container while it remains idle-retained. Smaller images, fewer heavyweight imports and lazy initialisation of rarely used clients all shorten the cold path.

For latency-sensitive paths, provisioned concurrency keeps execution capacity ready, measured in provisioned concurrency units (PCUs). The allowed values depend on memory: at 128 MB the minimum and increment are 40 PCUs, at 256 MB 20 PCUs, and at 512 MB and above 10 PCUs. It is bounded by a separate regional limit on memory reserved for provisioned concurrency. Reserve it only for synchronous paths with a real latency objective, and check current pricing before enabling it widely.

Observability and failure modes

Enable function logs on the application so stdout and stderr are captured by OCI Logging, and log one structured line per invocation with the input key and outcome. Function metrics such as invocation counts, errors and execution duration are published to OCI Monitoring, where an error-rate alarm should be the first you create.

SymptomLikely causeFix
HTTP 504 or timeouts at low loadMemory set too low for the workRaise memory, re-measure duration
Function cannot reach an OCI APINo service gateway or NAT route from the subnetAdd the route and security rules
NotAuthorized from SDK callsDynamic group rule or policy does not matchCheck the rule against the function OCID and compartment
HTTP 413Payload over 6 MBPass an Object Storage reference instead
Throttled invocationsTenancy memory limit reachedRequest a limit increase, lower memory per call
Runaway invocationsFunction's output re-triggers its own event rulePrefix filter in code and in the rule
Detached failures nobody sawNo failure destination configuredSend failures to a Queue and alarm on depth

Where Functions is the wrong shape: long-lived connections, work over an hour, steady high-throughput services that would cost less on always-on compute, and anything that needs local state between requests. OCI Container Instances or Kubernetes fit those better; Functions earns its place for bursty, event-driven, short tasks where paying nothing at idle matters.

What to do next

  1. Install the Fn CLI, configure a context for your region and OCIR repository, and deploy the generated hello-world function into a private subnet.
  2. Create a dynamic group scoped to that function and grant it one narrowly conditioned policy; test that a call outside the scope fails.
  3. Build the thumbnail pipeline: enable object events, add the Events rule, and verify the output-prefix guard stops self-triggering.
  4. Measure duration and cost at 256, 512 and 1024 MB and pick the cheapest size that meets your latency target.
  5. Move one long batch step to detached mode with a failure destination on a Queue, and alarm on queue depth.
  6. Turn on function logs, add error-rate and throttling alarms, and check concurrency against your tenancy memory limit.
Key takeaway: OCI Functions is Fn Project containers run on demand inside your VCN: an application owns subnets and config, a function is an OCIR image with memory and timeout, and identity comes from resource principals scoped by dynamic groups. Design around 6 MB payloads, 300-second synchronous calls and detached runs of up to an hour with result destinations, size memory for CPU, guard against self-triggering events, and keep every handler idempotent.