function-starlark supports multiple ways to organize and share Starlark code. This guide covers all source modes, module loading patterns, and caching behavior.
There are three ways to provide Starlark source code to function-starlark.
The simplest approach. Embed the script directly in the Composition YAML via
spec.source. Best for small compositions (< 100 lines) and quick prototyping.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
mode: Pipeline
pipeline:
- step: run-starlark
functionRef:
name: function-starlark
input:
apiVersion: starlark.fn.crossplane.io/v1alpha1
kind: StarlarkInput
spec:
source: |
region = get(oxr, "spec.region", "us-east-1")
Resource("bucket", {
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"spec": {"forProvider": {"region": region}},
})Store the script in a Kubernetes ConfigMap and reference it by name and key via
spec.scriptConfigRef. Best for sharing scripts across compositions, separating
code from YAML, and GitOps workflows where scripts are managed separately.
# ConfigMap holding the script
apiVersion: v1
kind: ConfigMap
metadata:
name: my-scripts
namespace: crossplane-system
data:
main.star: |
region = get(oxr, "spec.region", "us-east-1")
Resource("bucket", {
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"spec": {"forProvider": {"region": region}},
})# StarlarkInput referencing the ConfigMap
apiVersion: starlark.fn.crossplane.io/v1alpha1
kind: StarlarkInput
spec:
scriptConfigRef:
name: my-scripts
key: main.star # default key if omittedThe ConfigMap must be mounted into the function pod via a DeploymentRuntimeConfig. See the deployment guide for mount configuration.
Starlark modules packaged and distributed as OCI artifacts. Best for shared libraries across teams and clusters, and versioned module distribution.
When a default OCI registry is configured, use the concise short-form syntax:
load("function-starlark-stdlib:v1/naming.star", "resource_name")The short-form package:tag/file.star is expanded using the configured default
registry. For example, with registry ghcr.io/wompipomp, the load above
becomes oci://ghcr.io/wompipomp/function-starlark-stdlib:v1/naming.star.
For cases where you need to specify the full registry path, or when no default
registry is configured, use the explicit oci:// form:
load("oci://ghcr.io/my-org/starlark-lib:v1/helpers.star", "create_bucket")OCI modules are resolved before any Starlark code runs, preserving sandbox hermeticity. For the full guide on registry setup, pushing, versioning, and authentication, see the OCI module distribution guide.
The default OCI registry tells function-starlark where to find short-form module references. There are two configuration methods, and the spec field takes precedence over the environment variable.
1. Environment variable (operator-level)
Set STARLARK_OCI_DEFAULT_REGISTRY on the function pod via a
DeploymentRuntimeConfig. This applies to all compositions using this function
instance:
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: function-starlark
spec:
deploymentTemplate:
spec:
template:
spec:
containers:
- name: package-runtime
env:
- name: STARLARK_OCI_DEFAULT_REGISTRY
value: "ghcr.io/my-org"2. Spec field (per-composition override)
Set spec.ociDefaultRegistry in the StarlarkInput to override the
environment variable for a specific composition:
apiVersion: starlark.fn.crossplane.io/v1alpha1
kind: StarlarkInput
spec:
ociDefaultRegistry: "ghcr.io/my-org"
source: |
load("my-starlark-lib:v1/helpers.star", "create_bucket")
Resource("bucket", create_bucket("us-east-1"))| Priority | Source | Scope |
|---|---|---|
| 1 (highest) | spec.ociDefaultRegistry |
Per-composition |
| 2 | STARLARK_OCI_DEFAULT_REGISTRY env var |
All compositions on this function pod |
If spec.ociDefaultRegistry is set (non-empty), it wins. Otherwise the
environment variable is used. If neither is configured and a short-form load
target is encountered, the function returns a fatal error:
load target "function-starlark-stdlib:v1/naming.star" requires a default OCI registry;
set STARLARK_OCI_DEFAULT_REGISTRY env var on the function pod or spec.ociDefaultRegistry in function input
If you use the function-starlark VS Code extension, short-form load targets also need a default registry for schema IntelliSense (autocomplete, type checking, diagnostics). Set it in VS Code settings:
{
"functionStarlark.schemas.registry": "ghcr.io/my-org"
}The default is ghcr.io/wompipomp. If your runtime uses a different registry,
make sure the VS Code setting matches so that editor IntelliSense resolves the
same schemas as your deployed compositions.
function-starlark determines the load type using these rules, applied in order:
- Starts with
oci://-- explicit full OCI URL (no expansion, used as-is) - Contains
:or@sha256:-- short-form OCI reference, expanded via the default registry - Otherwise -- local module (inline or filesystem)
# Tag reference
load("my-lib:v1/helpers.star", "create_bucket")
# Nested file path
load("my-lib:v1/subdir/utils.star", "validate")
# Digest pinning (deterministic, skips tag resolution)
load("my-lib@sha256:abc123.../helpers.star", "create_bucket")Given a default registry of ghcr.io/my-org:
| Short-form | Expands to |
|---|---|
my-lib:v1/helpers.star |
oci://ghcr.io/my-org/my-lib:v1/helpers.star |
my-lib:v1/sub/utils.star |
oci://ghcr.io/my-org/my-lib:v1/sub/utils.star |
my-lib@sha256:abc.../h.star |
oci://ghcr.io/my-org/my-lib@sha256:abc.../h.star |
The registry value format is host/namespace (e.g., ghcr.io/my-org). Do not
include oci:// in the registry value -- it is stripped silently if present.
The load() statement imports names from other modules:
load("source", "name1", "name2")- First argument is the module source (string).
- Remaining arguments are names to import.
- Aliased imports are supported:
load("module.star", renamed = "original") - Star imports bring in all public exports:
load("module.star", "*")
Namespace alias imports wrap all public exports in a struct bound to a name
you choose. Use the syntax load("module.star", alias="*") where alias is
any valid Starlark identifier:
load("helpers.star", h="*")
h.my_function()
h.MY_CONSTANTAccess all exports via dot notation on the alias struct.
Solving name conflicts across provider packages. When two modules export the
same name (e.g., Azure storage and cosmosdb both export Account), flat
star imports clash. Namespace aliases solve this by keeping each module's exports
in a separate struct:
load("oci://ghcr.io/wompipomp/schemas-k8s:v1.35/apps/v1.star", k8s="*")
load("oci://ghcr.io/wompipomp/schemas-azure:v2.5.0/storage/v1.star", storage="*")
load("oci://ghcr.io/wompipomp/schemas-azure:v2.5.0/cosmosdb/v1.star", cosmosdb="*")
k8s.Deployment(...)
storage.Account(...) # no conflict with cosmosdb.Account
cosmosdb.Account(...)Backwards compatibility. Plain star imports still work exactly as before:
load("module.star", "*") # flat import -- all exports available at top levelMixed syntax. You can combine named imports with a namespace alias in the
same load() call. Named imports are available flat, while the namespace struct
contains everything:
load("module.star", "SpecificFunc", ns="*")
SpecificFunc() # available flat
ns.OtherFunc() # available via namespaceWhen load() is called, function-starlark resolves the module source in this
order:
- Inline modules (
spec.modules) -- keyed by filename - Module paths (
spec.modulePaths) -- filesystem directories - Short-form OCI modules (
package:tag/file.star) -- expanded via default registry - Explicit OCI modules (
oci://prefix) -- full OCI URL
# 1. Inline module (defined in spec.modules)
load("helpers.star", "my_func")
# 2. Short-form OCI module (requires default registry configured)
load("function-starlark-stdlib:v1/naming.star", "resource_name")
# 3. Explicit OCI module (full URL, no default registry needed)
load("oci://ghcr.io/myorg/starlark-libs/networking:v1.0.0/helpers.star", "subnet_cidr")
# 4. Standard library (using short-form with default registry)
load("function-starlark-stdlib:v1/networking.star", "subnet_cidr")
load("function-starlark-stdlib:v1/naming.star", "resource_name")
load("function-starlark-stdlib:v1/labels.star", "standard_labels")
load("function-starlark-stdlib:v1/conditions.star", "degraded")Define reusable modules directly in the StarlarkInput via spec.modules. Each
entry maps a filename to a Starlark script:
apiVersion: starlark.fn.crossplane.io/v1alpha1
kind: StarlarkInput
spec:
modules:
helpers.star: |
def make_bucket(name, region):
return {
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {"name": name},
"spec": {"forProvider": {"region": region}},
}
source: |
load("helpers.star", "make_bucket")
region = get(oxr, "spec.region", "us-east-1")
Resource("bucket", make_bucket("my-bucket", region))Inline modules can load other inline modules. Circular load dependencies are detected and produce a clear error.
function-starlark ships a standard library published as an OCI artifact at
ghcr.io/wompipomp/starlark-stdlib. It provides four modules covering common
composition patterns:
| Module | Functions | Purpose |
|---|---|---|
networking.star |
ip_to_int, int_to_ip, network_address, broadcast_address, subnet_cidr, cidr_contains |
CIDR math and IP utilities |
naming.star |
resource_name |
Kubernetes-safe naming with 63-char limit enforcement |
labels.star |
standard_labels, crossplane_labels, merge_labels |
Kubernetes and Crossplane label generation |
conditions.star |
degraded |
Operational status signaling |
# Short-form (recommended, requires default registry configured)
load("starlark-stdlib:v1/networking.star", "subnet_cidr")
# Explicit full URL (always works, no default registry needed)
load("oci://ghcr.io/wompipomp/starlark-stdlib:v1/networking.star", "subnet_cidr")
subnet = subnet_cidr("10.0.0.0/16", 8, 1) # "10.0.1.0/24"For full function signatures and documentation, see the standard library reference.
function-starlark uses two caching layers to minimize overhead across reconciliation cycles.
Compiled Starlark bytecode is cached in-memory. When the same script is executed again (e.g., on the next reconciliation), the cached bytecode is reused, skipping the parse and compile steps. Cache performance is tracked via Prometheus metrics:
function_starlark_cache_hits_total-- bytecode cache hitsfunction_starlark_cache_misses_total-- bytecode cache misses
OCI tag-to-digest mappings are cached in-memory on the function pod. The
resolver honors a Kubernetes-style pull policy set per pod via
STARLARK_OCI_PULL_POLICY:
IfNotPresent(default) — pull on first reference, then reuse the cached copy for the pod's lifetime. No revalidation, zero steady-state registry traffic. Treat tags as immutable; restart the pod (or pin a new tag/digest) to pick up a retag.Always— revalidate with a manifestHEADon cache miss or afterSTARLARK_OCI_CACHE_TTLelapses (default0means every reconciliation). Unchanged digest reuses cached content; a changed digest triggers a new pull. Use this when you intentionally push updates to moving tags and want in-place refresh without a pod restart.
Digest-pinned references (e.g., @sha256:abc123...) bypass the tag cache
entirely and are cached permanently regardless of policy, since a digest is
immutable. Recommended for production compositions.
OCI resolution time is tracked via the
function_starlark_oci_resolve_duration_seconds histogram.
For private OCI registries, set spec.dockerConfigSecret to the name of a
Kubernetes Secret containing Docker registry credentials:
apiVersion: starlark.fn.crossplane.io/v1alpha1
kind: StarlarkInput
spec:
dockerConfigSecret: my-registry-creds
source: |
load("oci://myregistry.azurecr.io/modules/helpers:v1/helpers.star", "create_bucket")The secret must be mounted into the function pod via a DeploymentRuntimeConfig. For complete authentication setup (ACR, ECR, GHCR), see the OCI module distribution guide.
For local development with crossplane render, use spec.dockerConfigCredential
instead — it passes Docker credentials via gRPC since crossplane render cannot
mount volumes into function containers. Both fields can coexist in the same
Composition (gRPC credential is tried first, filesystem secret second). See the
OCI module distribution guide
for setup instructions including Azure ACR token generation.
- OCI Module Distribution -- full guide for publishing, loading, and authenticating OCI modules
- Library Authoring Guide -- conventions for writing shared Starlark libraries
- Standard Library Reference -- complete API documentation for the built-in standard library