Building a Production‑Ready Serverless Go Backend on Oracle Cloud Infrastructure [OCI]
How we shipped a real, production‑ready “serverless Go on OCI” stack using the Fn Project, Oracle Cloud Functions, API Gateway, Object…
Building a Production‑Ready Serverless Go Backend on Oracle Cloud Infrastructure [OCI]

How we shipped a real, production‑ready “serverless Go on OCI” stack using the Fn Project, Oracle Cloud Functions, API Gateway, Object Storage, NoSQL, IAM, and Logging — complete with Terraform IaC and a push‑button deployment script.
TL;DR This guide walks through a working reference implementation — OCI Serverless Golang Backend — that you can deploy end‑to‑end on Oracle Cloud Infrastructure (OCI). You’ll provision the cloud resources with Terraform, build and deploy Go functions with Fn Project (Oracle Cloud Functions), secure them using resource principals, persist files in Object Storage, track metadata in NoSQL Database, and wire everything up behind API Gateway with centralized Logging. Code and IaC are here: **github.com/YISUSVII/oci-serverless-go**.
Why serverless Go on OCI?
Serverless lets you focus on code while OCI handles scaling, fault tolerance, and runtime security. When you combine Oracle Cloud Functions (powered by the Fn Project) with API Gateway, Object Storage, NoSQL, and Logging, you get a clean architecture that is:
- Elastic (auto‑scales to zero)
- Cost‑efficient (pay for invocation time)
- Secure by default (resource principal authentication — no secrets on disk)
- Observable (centralized logs in OCI Logging)
- Composable (mix Functions, API Gateway, Object Storage, NoSQL, IAM)
This article is both beginner‑friendly and technical. You can follow it to stand up your own production‑ready backend and adapt it to your needs.
What we’ll build
Project: OCI Serverless Golang Backend with three functions, each independently versioned:
- healthcheck (v0.0.16; Go 1.23) — simple liveness/diagnostics endpoint
Build image:
fnproject/go:1.18-dev· Run image:fnproject/go:1.18· Mem: 256 MB · Timeout: 30s - upload_document (v0.0.6; Go 1.23) — accepts PDF uploads, writes to Object Storage, records metadata in NoSQL
Build image:
fnproject/go:1.18-dev· Run image:fnproject/go:1.18· Mem: 256 MB · Timeout: 30s - data_ops (v0.0.5; Go 1.18) — CRUD over the metadata stored in NoSQL
Build image:
fnproject/go:1.18-dev· Run image:fnproject/go:1.18· Mem: 256 MB · Timeout: 30s
Key Go packages (and versions used in this project):
github.com/fnproject/fdk-go v0.0.65github.com/google/uuid v1.3.0github.com/oracle/oci-go-sdk/v65 v65.35.0(Object Storage, NoSQL, IAM auth)- Indirect deps likely to appear:
github.com/gofrs/flock v0.8.1,github.com/sony/gobreaker v0.5.0,github.com/stretchr/testify v1.11.1,golang.org/x/sys v0.6.0
Architecture overview (high level)

Components
- API Gateway — Front‑door routing, HTTPS termination, request validation.
- Functions (Fn Project / Oracle Cloud Functions) — Stateless Go functions.
- Object Storage — Persistent, durable storage for uploaded PDFs.
- NoSQL Database — Low‑latency metadata store (automatic indexing).
- IAM — Resource principal authentication (no API keys in code).
- Logging — Centralized logs for observability and troubleshooting.
Prerequisites & tools
- OCI CLI v3.17+ (configured with API keys, tenancy, region)
- Terraform v1.0+ (IaC for network, functions, API Gateway, NoSQL, Object Storage, Logging, IAM)
- Go 1.18+ (project uses both Go 1.18 and 1.23)
- Fn CLI (build & deploy functions to Oracle Cloud Functions)
- Docker (containerized builds)
- jq (JSON parsing in deployment scripts)
IMPORTANT : Cross‑platform builds are handled with
DOCKER_DEFAULT_PLATFORM=linux/amd64so your Apple Silicon laptop won’t ship ARM images to an AMD64 runtime.
Cloning the repo
git clone https://github.com/YISUSVII/oci-serverless-go
cd oci-serverless-go
The repository contains:
terraform/— IaC for API Gateway, Functions, IAM, NoSQL, Object Storage, Loggingfunctions/healthcheck,functions/upload_document,functions/data_opsdeploy_functions.sh— builds & deploys all functions via Fn CLI + Docker + jq
Step 1 — Provision infrastructure with Terraform
- Initialize modules and providers
cd terraform
terraform init
2. Review the execution plan
terraform plan -out tf.plan
3. Apply changes
terraform apply tf.plan
Terraform stands up:
- An Fn application with three Oracle Cloud Functions
- API Gateway with routes for
/health,/documents(POST),/metadata(GET/PUT/DELETE) - Object Storage bucket for PDFs
- NoSQL table for metadata (automatic indexing enabled)
- OCI Logging sink for function logs
- IAM policies and a dynamic group for resource principal auth
Example (simplified) policy ideas — adjust to “least privilege” in your compartment:
Allow dynamic-group <functions-dg> to manage objects in compartment <your-compartment>
Allow dynamic-group <functions-dg> to use nosql-family in compartment <your-compartment>
Allow service apigateway to use functions-family in compartment <your-compartment>
(Optional) API Gateway mapping with Terraform (illustrative)
resource "oci_apigateway_deployment" "api" {
# ...
path_prefix = "/v1"
path_routes {
path = "/health"
methods = ["GET"]
backend {
type = "ORACLE_FUNCTIONS_BACKEND"
function_id = oci_functions_function.healthcheck.id
}
}
path_routes {
path = "/documents"
methods = ["POST"]
backend {
type = "ORACLE_FUNCTIONS_BACKEND"
function_id = oci_functions_function.upload_document.id
}
}
path_routes {
path = "/metadata"
methods = ["GET", "PUT", "DELETE"]
backend {
type = "ORACLE_FUNCTIONS_BACKEND"
function_id = oci_functions_function.data_ops.id
}
}
}
Step 2 — Function configuration (Fn Project)
Each function is configured with Fn’s func.yaml. This project pins the same base images and resources across functions:
# functions/healthcheck/func.yaml
name: healthcheck
version: 0.0.16
runtime: go
build_image: fnproject/go:1.18-dev
run_image: fnproject/go:1.18
memory: 256
timeout: 30
# functions/upload_document/func.yaml
name: upload_document
version: 0.0.6
runtime: go
build_image: fnproject/go:1.18-dev
run_image: fnproject/go:1.18
memory: 256
timeout: 30
# functions/data_ops/func.yaml
name: data_ops
version: 0.0.5
runtime: go
build_image: fnproject/go:1.18-dev
run_image: fnproject/go:1.18
memory: 256
timeout: 30
Although healthcheck and upload_document target Go 1.23 and data_ops uses Go 1.18, the build/run images shown above are standardized on
fnproject/go:1.18-dev/1.18. In practice, keep your code compatible with the image you build with (or update the images to a newer Go tag if you rely on 1.23‑only features).
Step 3 — Code examples (Go + FDK)
All functions use **github.com/fnproject/fdk-go v0.0.65**. Below are trimmed versions to highlight the core ideas. (Omitted error handling and some types for clarity.)
1) healthcheck (Go 1.23)
**go.mod (excerpt)**
module example.com/healthcheck
go 1.23
require github.com/fnproject/fdk-go v0.0.65
**func.go (simplified)**
package main
import (
"context"
"encoding/json"
"io"
"net/http"
"os"
"runtime"
"time"
fdk "github.com/fnproject/fdk-go"
)
type hc struct {
Status string `json:"status"`
Time string `json:"time"`
GoVersion string `json:"goVersion"`
Region string `json:"region,omitempty"`
Compartment string `json:"compartment,omitempty"`
}
func main() {
fdk.Handle(fdk.HandlerFunc(handle))
}
func handle(ctx context.Context, in io.Reader, out io.Writer) {
resp := hc{
Status: "ok",
Time: time.Now().UTC().Format(time.RFC3339),
GoVersion: runtime.Version(),
Region: os.Getenv("OCI_REGION"),
}
w := json.NewEncoder(out)
// Fn expects an HTTP-like response over the stream format.
_ = w.Encode(resp) // handle errors in real code
// Optionally write headers with fdk.SetResponseHeader(out, "Content-Type", "application/json")
_ = http.StatusOK
}
2) upload_document (Go 1.23)
**go.mod (excerpt)**
module example.com/upload_document
go 1.23
require (
github.com/fnproject/fdk-go v0.0.65
github.com/google/uuid v1.3.0
github.com/oracle/oci-go-sdk/v65 v65.35.0
)
**func.go (core flow; resource principals; Object Storage + NoSQL)**
package main
import (
"context"
"encoding/base64"
"encoding/json"
"io"
"time"
fdk "github.com/fnproject/fdk-go"
"github.com/google/uuid"
"github.com/oracle/oci-go-sdk/v65/common/auth"
"github.com/oracle/oci-go-sdk/v65/objectstorage"
"github.com/oracle/oci-go-sdk/v65/nosql"
)
type UploadRequest struct {
FileName string `json:"fileName"`
ContentBase64 string `json:"contentBase64"` // for demo; in production prefer multipart/form-data
Metadata map[string]string `json:"metadata"`
}
type UploadResponse struct {
ID string `json:"id"`
ObjectURI string `json:"objectUri"`
}
func main() {
fdk.Handle(fdk.HandlerFunc(handler))
}
func handler(ctx context.Context, in io.Reader, out io.Writer) {
var req UploadRequest
_ = json.NewDecoder(in).Decode(&req)
id := uuid.New().String()
b, _ := base64.StdEncoding.DecodeString(req.ContentBase64)
// Resource principal provider: no keys needed
provider, _ := auth.ResourcePrincipalConfigurationProvider()
// 1) Put the PDF into Object Storage
osClient, _ := objectstorage.NewObjectStorageClientWithConfigurationProvider(provider)
bucket := "pdf-uploads" // set via env/config
namespace := "mytenancyns" // fetch via osClient.GetNamespace
putReq := objectstorage.PutObjectRequest{
NamespaceName: &namespace,
BucketName: &bucket,
ObjectName: &req.FileName,
PutObjectBody: io.NopCloser(bytes.NewReader(b)),
ContentType: common.String("application/pdf"),
}
_, _ = osClient.PutObject(ctx, putReq)
// 2) Insert metadata into NoSQL
noClient, _ := nosql.NewNosqlClientWithConfigurationProvider(provider)
table := "documents"
item := map[string]any{
"id": id,
"fileName": req.FileName,
"bucket": bucket,
"createdAt": time.Now().UTC().Format(time.RFC3339),
"metadata": req.Metadata,
}
_ = putRow(ctx, noClient, table, item) // implement with nosql.PutRowRequest
_ = json.NewEncoder(out).Encode(UploadResponse{
ID: id,
ObjectURI: "oci:///" + bucket + "/" + req.FileName,
})
}
Notes: • The OCI Go SDK v65.35.0 exposes Resource Principal auth via
common/auth. • For NoSQL, use thenosqlclient’sPutRow,GetRow,UpdateRow,DeleteRowrequests. In production, validate file type, limit sizes, and handle transactional upserts carefully.
3) data_ops (Go 1.18)
This function exposes CRUD for metadata stored in NoSQL.
**go.mod (excerpt)**
module example.com/data_ops
go 1.18
require (
github.com/fnproject/fdk-go v0.0.65
github.com/google/uuid v1.3.0
github.com/oracle/oci-go-sdk/v65 v65.35.0
)
**func.go (CRUD sketch)**
package main
import (
"context"
"encoding/json"
"io"
"net/http"
fdk "github.com/fnproject/fdk-go"
"github.com/oracle/oci-go-sdk/v65/common/auth"
"github.com/oracle/oci-go-sdk/v65/nosql"
)
func main() { fdk.Handle(fdk.HandlerFunc(route)) }
func route(ctx context.Context, in io.Reader, out io.Writer) {
// Parse a simple envelope: { "op": "get|put|delete|list", "id": "...", "item": {...} }
var req map[string]any
_ = json.NewDecoder(in).Decode(&req)
provider, _ := auth.ResourcePrincipalConfigurationProvider()
noClient, _ := nosql.NewNosqlClientWithConfigurationProvider(provider)
table := "documents"
// Switch by op and call helpers (getRow, putRow, deleteRow, queryRows)
// Each helper uses nosql.* requests with table name/id and key attributes.
// Finally, write JSON response with status and rows/items.
_ = http.StatusOK
}
Step 4 — Deploy functions with the script
The repository includes a helper script that builds and deploys all functions with Fn CLI and Docker. It also shows jq usage to extract IDs if you need to wire them back into Terraform or logs.
**deploy_functions.sh (excerpt)**
#!/usr/bin/env bash
set -euo pipefail
export DOCKER_DEFAULT_PLATFORM=linux/amd64
export FN_APP_NAME="oci-serverless-go"
export FN_REGISTRY="iad.ocir.io/<tenancy-namespace>/<repo>" # set yours
echo "Building & deploying functions to app: ${FN_APP_NAME}"
for dir in functions/healthcheck functions/upload_document functions/data_ops; do
pushd "$dir" >/dev/null
echo "Tidying modules in $(pwd)"
go mod tidy
echo "Deploying $(basename "$dir")"
fn -v deploy --app "${FN_APP_NAME}" --registry "${FN_REGISTRY}"
# Optional: capture function OCID (output may vary)
FN_INFO=$(fn inspect function "${FN_APP_NAME}" "$(basename "$dir")" --format json || true)
FN_OCID=$(echo "${FN_INFO}" | jq -r '.id // empty')
[[ -n "${FN_OCID}" ]] && echo "$(basename "$dir") OCID: ${FN_OCID}"
popd >/dev/null
done
echo "Done."
Why the
DOCKER_DEFAULT_PLATFORM=linux/amd64export? It ensures images built on Apple Silicon or other ARM hosts run reliably in the AMD64 environment used by the Fn runtime on OCI.
Step 5 — Test the endpoints
Once Terraform has created the API Gateway deployment and you’ve deployed the functions, you’ll have a public base URL (e.g., https://<gateway-id>.apigateway.<region>.oci.customer-oci.com/v1).
- Healthcheck
curl -s https://<gateway>/v1/health | jq
- Upload a document (simplified base64 JSON flow)
PDF_B64=$(base64 -w 0 sample.pdf) curl -s -X POST https://<gateway>/v1/documents \ -H 'Content-Type: application/json' \ -d "{\"fileName\":\"sample.pdf\",\"contentBase64\":\"${PDF_B64}\",\"metadata\":{\"source\":\"web\"}}" | jq
- Metadata ops (examples)
# GET curl -s -X GET https://<gateway>/v1/metadata?id=<doc-id> | jq
# PUT (update metadata) curl -s -X PUT https://<gateway>/v1/metadata
\ -H 'Content-Type: application/json' \
-d '{"id":"<doc-id>","item":{"tags":["receipt","2025"]}}' | jq
# DELETE curl -s -X DELETE "https://<gateway>/v1/metadata?id=<doc-id>"
Troubleshooting (save yourself hours)
1) “exec format error” or image architecture mismatch
- Symptom: Function fails after deploy; logs mention bad exec format.
- Fix: Build for AMD64 explicitly:
export DOCKER_DEFAULT_PLATFORM=linux/amd64 fn deploy ...
2) Authentication errors (“not authorized”, “signature invalid”)
- Symptom: Calls to Object Storage / NoSQL fail from inside the function.
- Fixes:
- Ensure you are using Resource Principal auth:
provider, err := auth.ResourcePrincipalConfigurationProvider()
- Verify your dynamic group matches your function resources.
- Add policies with least privilege for objectstorage and nosql in the target compartment.
3) API Gateway 404 / route not found
- Symptom: API returns 404 even though function exists.
- Fixes:
- Check path prefix + route path in the deployment (e.g.,
/v1+/health). - Make sure
methodsinclude your verb (GET,POST, etc.). - Confirm the gateway deployment is active and the function OCIDs are correct.
4) Timeouts on large uploads
- Symptom:
upload_documenttimes out at ~30s. - Fixes:
- Start by increasing
timeoutinfunc.yaml(e.g., to 60 or 120). - Consider a pre‑signed URL pattern to upload directly to Object Storage, then call a light‑weight metadata function.
5) Go version & base image drift
- Symptom: You’re using Go 1.23 features, but the
fnproject/gobase image is 1.18. - Fixes:
- Either keep your code within 1.18‑compatible features or update the build/run images to a newer Go tag across functions to match your toolchain.
6) “Bucket not found” or 404 on PutObject
- Fixes: Confirm namespace and bucket name; use the correct region; ensure policies grant your dynamic group permissions to manage objects.
7) Observability tips
- Logs go to OCI Logging automatically (stdout/stderr).
- Add request IDs to logs for easier correlation.
- Store function OCIDs and API Gateway IDs in outputs (Terraform) and annotate dashboards.
Best practices & lessons learned
- Use resource principals — it’s the cleanest way to do server‑to‑service auth on OCI. No secrets, no rotated keys in CI.
- Split responsibilities — we separated
upload_document(file writes + metadata) fromdata_ops(metadata CRUD) and ahealthcheckfor basic diagnostics. That keeps handlers small, testable, and failure‑isolated. - IaC everything — Terraform tracks OCIDs and relationships (e.g., mapping API Gateway routes to function OCIDs). Version your infra like you version code.
- Keep functions stateless — focus on idempotence and pass context/IDs around; rely on NoSQL for state.
- Optimize cold starts — bundle only what you need; keep image layers small.
- Standardize memory/timeouts — we used 256 MB and 30s, which fits many I/O‑bound tasks. Adjust if you do heavy parsing or large uploads.
- Version independently — this project tags functions separately (
healthcheck v0.0.16,upload_document v0.0.6,data_ops v0.0.5). It makes rollbacks and staged releases safer. - Validate inputs — especially for uploads. Enforce
Content-Type: application/pdf, size limits, checksum, and virus scanning as appropriate.
Feature recap (what you get out of the box)
- ✅ API Gateway with clean routes and HTTPS
- ✅ Oracle Cloud Functions (Fn Project) running Go handlers
- ✅ Object Storage for durable PDF storage
- ✅ NoSQL Database for metadata with automatic indexing
- ✅ IAM Resource Principals for keyless intra‑cloud auth
- ✅ Centralized Logging for observability and debugging
- ✅ Terraform v1.0+ IaC to reproduce environments
- ✅ Push‑button deploy via
deploy_functions.sh(Fn CLI + Docker + jq) - ✅ Cross‑platform builds with
DOCKER_DEFAULT_PLATFORM=linux/amd64
Where to go next
- Add request validation and rate limiting at API Gateway.
- Move to multipart/form-data uploads and/or pre‑signed URLs for large files.
- Introduce gobreaker (circuit breaker — already listed as an indirect dep) around NoSQL/Object Storage calls if you expect transient errors.
- Implement structured logging and correlation IDs.
- Add integration tests (
stretchr/testifyis already in deps) and CI/CD. - Consider async processing via OCI Streaming/Queue for heavy post‑upload tasks (OCR, virus scanning, indexing).
Conclusion
If you’re looking for a serverless Go on OCI blueprint that scales from “hello world” to production, this stack hits the sweet spot: small, composable functions; security with resource principals; durable storage; a low‑latency NoSQL control plane; and a clean, automated Terraform deployment. It’s simple enough for a first project and robust enough to run in production.
👉 Explore the code and IaC here: **github.com/YISUSVII/oci-serverless-go** ⭐ If it helps you, star the repo and share what you build with it.
메타데이터
- post_id
- 7c41bf0ce0ff
- slug
- building-a-production-ready-serverless-go-backend-on-oracle-cloud-infrastructure-oci-7c41bf0ce0ff
- url
- https://medium.com/@yisusvii/building-a-production-ready-serverless-go-backend-on-oracle-cloud-infrastructure-oci-7c41bf0ce0ff
- canonical_url
- https://medium.com/@yisusvii/building-a-production-ready-serverless-go-backend-on-oracle-cloud-infrastructure-oci-7c41bf0ce0ff
- author_url
- https://medium.com/@yisusvii
- status
- ok
- fetched_at
- 2026-07-15 07:46:41