Configure SDKs with a Declarative YAML File
Use one YAML file with OTEL_CONFIG_FILE instead of dozens of environment variables, where your SDK supports it.
Environment variables work well for a handful of settings, but they get unwieldy when you need multiple exporters, custom processors, or sampling rules. Declarative configuration replaces them with a single YAML file that describes the whole SDK: resource, propagators, and the tracer, meter, and logger providers. The schema reached a stable release in 2026, which means it is safe to design around. Support in individual SDKs is still uneven, so read the status section before you adopt it.
Turn it on
Save a file and point the SDK at it with one environment variable:
export OTEL_CONFIG_FILE=/etc/otel/otel-config.yaml
When OTEL_CONFIG_FILE is set, an SDK that supports declarative configuration builds itself from the file. Other OTEL_* variables are then ignored unless the file references them, which is described below.
A complete example
otel-config.yaml:
file_format: "1.2"
resource:
attributes_list: ${OTEL_RESOURCE_ATTRIBUTES}
detection/development:
detectors:
- service:
- host:
- process:
- container:
propagator:
composite:
- tracecontext:
- baggage:
tracer_provider:
sampler:
parent_based:
root:
always_on:
processors:
- batch:
exporter:
otlp_http:
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/traces
meter_provider:
readers:
- periodic:
exporter:
otlp_http:
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/metrics
logger_provider:
processors:
- batch:
exporter:
otlp_http:
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/logs
Use the file_format value that your SDK’s documentation specifies, since SDKs adopt new schema versions at different times. How to read the file:
resourcedescribes the service.attributes_listtakes the usualkey=value,key=valuestring, anddetectorsadd host, process, and container information automatically. Properties ending in/developmentare still experimental and may not exist in every SDK.propagatorlists the context propagators, here W3C trace context and baggage.tracer_providerholds the sampler and the span processors. Here a batch processor sends spans through an OTLP HTTP exporter.meter_providerholds metric readers. Aperiodicreader exports on an interval.logger_providerdoes the same for logs.otlp_httpvsotlp_grpc: swap the exporter name and endpoint (http://localhost:4317for gRPC) to change protocols.
Environment variable substitution
Inside the file, ${VAR} reads an environment variable and ${VAR:-default} supplies a default when it’s unset. Environment variables are otherwise ignored, which is the point: nothing changes behavior unless the file asks for it. That makes it easy to see what is configurable per environment:
resource:
attributes_list: ${OTEL_RESOURCE_ATTRIBUTES}
tracer_provider:
processors:
- batch:
exporter:
otlp_http:
endpoint: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-http://localhost:4318/v1/traces}
The same file then works in development (defaults) and production (the platform sets the variables).
Per-signal destinations and richer setups
Declarative configuration shines for things that environment variables can’t express well, such as sending the same spans to two places or tuning a processor:
tracer_provider:
processors:
- batch:
schedule_delay: 2000
max_queue_size: 4096
exporter:
otlp_http:
endpoint: http://collector:4318/v1/traces
- simple:
exporter:
console:
Here spans go to a Collector in batches and also print to the console, which is useful for local debugging. Check your SDK’s documentation for the exact property names it supports, because the available processors, samplers, and exporters depend on the implementation.
Which languages support it
The specification and schema are stable, but each language implements them on its own schedule:
- Available: C++, Go, Java, JavaScript, and PHP have implementations, and the Java agent documents its own setup.
- In development: .NET and Python.
- The documentation still describes support across implementations as experimental, so treat the feature as one to test for your language before depending on it.
- The Collector itself uses the Go implementation to configure its internal telemetry.
If your language isn’t ready, keep using environment variables. The two models are intentionally compatible: the variable names shown in the substitution examples are the same ones you would use without a file.
Choosing between the two
| Use environment variables when | Use a declarative file when |
|---|---|
| You set a few options per service | You need multiple exporters or processors |
| Your platform already injects the environment (Operator, Helm) | You want the whole SDK setup reviewed in one file |
| Your language’s declarative support is missing or immature | You want the same config format across several languages |
A reasonable hybrid is to mount a shared file from a ConfigMap and let environment variables supply only the per-environment values through ${...} substitution.
Deliver the file
Docker Compose:
services:
checkout:
image: myorg/checkout:1.4.2
environment:
OTEL_CONFIG_FILE: /etc/otel/otel-config.yaml
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318
OTEL_RESOURCE_ATTRIBUTES: service.version=1.4.2,deployment.environment.name=staging
volumes:
- ./otel-config.yaml:/etc/otel/otel-config.yaml:ro
Kubernetes: put the file in a ConfigMap, mount it, and set OTEL_CONFIG_FILE in the Deployment. A change to the ConfigMap in Git then rolls out like any other change, but SDKs read the file at startup, so restart the pods to pick it up.
Gotchas
- A mistake in the file can stop the SDK from starting its providers. Test it in a pre-production environment, and watch the SDK’s log output at startup (
OTEL_LOG_LEVEL=debugtemporarily). - Don’t put secrets directly in the file. Use
${VAR}and inject the value from a Secret. - Dynamic reconfiguration at runtime is not part of this specification yet. Changing the file requires a restart.
- Some individual environment variables are planned for deprecation because they don’t interoperate well, though deprecated does not mean removed.
For the full schema and per-language notes, see the declarative configuration documentation.
Popular Guides
-
OpenTelemetry Explained: SDKs, OTLP, and the Collector Understand the moving parts and the three places configuration lives before you write any YAML. -
The Collector Configuration Model: Components, Pipelines, and Providers Learn the rules of the Collector's YAML: naming, pipelines, connectors, merging files, and environment variables. -
Configure SDKs with OTEL_* Environment Variables Set the service name, exporters, sampling, and limits for any language using standard variables. -
Instrument Apps Without Code Changes: Java, Python, and Node.js Attach OpenTelemetry agents with environment variables and command-line flags only. -
Make the Collector Production-Ready: Memory Limits, Batching, and Retries Configure processors, queues, and persistent storage so telemetry survives overload and outages. -
Deploy the Collector on Kubernetes with Helm Install the official chart, use presets for logs and metrics, and override the config through values files. -
Troubleshoot the Collector and SDKs A systematic checklist for when telemetry doesn't arrive: debug exporter, health checks, internal metrics, and common mistakes.