OpenTelemetry OpenTelemetry

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.

10 min Intermediate

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:

  • resource describes the service. attributes_list takes the usual key=value,key=value string, and detectors add host, process, and container information automatically. Properties ending in /development are still experimental and may not exist in every SDK.
  • propagator lists the context propagators, here W3C trace context and baggage.
  • tracer_provider holds the sampler and the span processors. Here a batch processor sends spans through an OTLP HTTP exporter.
  • meter_provider holds metric readers. A periodic reader exports on an interval.
  • logger_provider does the same for logs.
  • otlp_http vs otlp_grpc: swap the exporter name and endpoint (http://localhost:4317 for 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 whenUse a declarative file when
You set a few options per serviceYou 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 immatureYou 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=debug temporarily).
  • 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