OpenTelemetry’s graduation within the Cloud Native Computing Foundation was an important milestone. It confirmed that the project’s APIs, governance, and release processes had reached a level of maturity suitable for long-term adoption.
However, graduation doesn’t solve one practical problem: The distribution containing the Collector you need may not include all the components your environment requires.
One distribution might provide the receiver you need, while another includes the processor or exporter you want. If neither distribution contains the complete combination, you’re usually left with two choices:
But there’s a third option: Build your own collector distribution with the OpenTelemetry Collector Builder, or OCB. OCB allows you to assemble a collector containing only the components required by your use case.
This article explains how to build a custom collector with OCB, using an OpenShift and Elastic deployment as an example.
Suppose you’re running an OpenShift cluster and want to collect and monitor telemetry from it. The Red Hat build of OpenTelemetry is a sensible starting point. As discussed in my previous article, it provides a curated and supported set of OpenTelemetry components.
However, it may not include all the processors and exporters you need for a particular observability platform.
In this example, we want to use Elastic for monitoring and would like the collector to handle Elastic-specific enrichment and processing natively. In particular, we want to:
Consider this common situation: A platform-specific distribution provides most of what you need, but it’s missing components from a particular vendor or ecosystem. OCB provides a way to combine those pieces into a single binary.
There is no universally correct distribution to use as a starting point. The OpenTelemetry project provides both the core collector and the contrib distribution. There are also distributions maintained by vendors such as Red Hat, Elastic, Grafana, and AWS.
You can use any of these as a reference, or start from an empty manifest and define the required components yourself. In this article, we use the OpenShift and Elastic combination because it represents a realistic deployment scenario. The same process applies to other platforms and vendors.
You’ll need two things before building a collector:
OCB is written in Go, so the Go toolchain must be installed before you build or install it. Use the Go version recommended by the Collector release you plan to use.
For this example, we’ll use OCB version 0.156.0:
# curl --proto '=https' --tlsv1.2 -fL -o ocb \
https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/cmd/builder/v0.156.0/ocb_0.156.0_linux_amd64
# chmod +x ocb
Version alignment is important when building a custom collector. The OCB version, the OpenTelemetry components in the manifest, and third-party components must be compatible with one another. Using arbitrary versions can cause Go dependency conflicts or compilation errors when APIs have changed or been removed.
The version numbers do not all need to be identical. Each component is identified by its Go module path and its own version, and different projects may follow different release cycles. For example, the core OpenTelemetry Collector components and the OCB builder may use v0.156.0, Elastic’s Collector components may use v0.68.0, and the OpenTelemetry configuration-provider modules may use v1.62.0. These numbers cannot be compared directly: v1.62.0 is not necessarily newer than v0.156.0, because they belong to different modules. What matters is that the selected versions expose compatible APIs and are tested together. For example, the following combination can be valid if the Elastic v0.68.0 components support the Collector APIs used by v0.156.0:
dist:
otelcol_version: "0.156.0"
processors:
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.156.0
- gomod: github.com/elastic/opentelemetry-collector-components/processor/elasticapmprocessor v0.68.0
providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.62.0
While an incompatible dependency set may result in errors such as: undefined: xconfmap.Validator
Using the latest version of every component is tempting, but it is not always the safest approach. Pin the versions deliberately and update them together after testing the resulting Collector.
A useful way to approach the manifest is to start with the smallest set of components that meet your requirements.
Include only the receivers, processors, connectors, exporters, extensions, and configuration providers that you actually need. This keeps the resulting binary smaller and makes its behavior easier to understand and maintain. It also avoids shipping unused components that may increase the operational and security footprint of the image.
In our example we want at least:
Create a file named manifest.yaml with the following content:
dist:
name: otelcol-neteye
description: Custom OpenTelemetry Collector for NetEye (Elastic-ready)
output_path: ./otelcol-neteye
version: 1.0.0
otelcol_version: "0.156.0"
receivers:
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.156.0
- gomod: github.com/elastic/opentelemetry-collector-components/receiver/elasticapmintakereceiver v0.68.0
processors:
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.156.0
- gomod: github.com/elastic/opentelemetry-collector-components/processor/elasticapmprocessor v0.68.0
connectors:
- gomod: github.com/elastic/opentelemetry-collector-components/connector/elasticapmconnector v0.68.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.156.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/elasticsearchexporter v0.156.0
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.156.0
extensions:
- gomod: github.com/elastic/opentelemetry-collector-components/extension/apmconfigextension v0.68.0
providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.62.0
- gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.62.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpprovider v1.62.0
- gomod: go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.62.0
- gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.62.0
replaces:
- github.com/elastic/opentelemetry-collector-components/internal/elasticattr => github.com/elastic/opentelemetry-collector-components/internal/elasticattr v0.68.0
The manifest is declarative, describing the components that should be included in the final distribution. OCB then generates the source code and builds our collector from that definition.
./ocb --config manifest.yaml
The output will appear in the output_path directory defined in your manifest (in this case, ./otelcol-neteye). You can verify that the build succeeded by checking for the binary executable (otelcol-neteye).
Once built, the binary is configured exactly like any other OTel Collector, it needs a YAML configuration file and nothing else. Here’s an example.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
connectors:
elasticapm: {}
processors:
batch: {}
elasticapm: {}
exporters:
debug:
verbosity: detailed
elasticsearch/otel:
endpoints: ["https://your-elasticsearch:9200"]
api_key: ${env:ELASTIC_API_KEY}
mapping:
mode: otel
service:
pipelines:
traces:
receivers: [otlp]
processors: [elasticapm]
exporters: [elasticapm, elasticsearch/otel, debug]
metrics:
receivers: [otlp]
exporters: [elasticsearch/otel, debug]
metrics/aggregated-otel-metrics:
receivers: [elasticapm]
exporters: [elasticsearch/otel]
logs:
receivers: [otlp]
exporters: [elasticsearch/otel, debug]
And now we’re ready to test it.
Validate the configuration: ./build/otelcol-neteye validate --config config.yaml
Run it and watch for errors: Using the debug exporter you’ll be able to see if there are any issues with the configuration ./build/otelcol-neteye --config config.yaml
Send some telemetry: The Collector-contrib repository ships telemetrygen, a small load generator, which you can use to generate sample telemetry:
git clone https://github.com/open-telemetry/opentelemetry-collector-contrib.git
cd opentelemetry-collector-contrib/cmd/telemetrygen
go build -o telemetrygen .
./telemetrygen traces --otlp-endpoint localhost:4317 --otlp-insecure --traces 5
If everything is wired up correctly, you should see the spans printed by the debug exporter in your collector’s logs, confirming that we have our own working OpenTelemetry Collector!
Running a local binary is fine for testing and for a handful of use cases. Most of the time though you’ll want a collector packaged into a neat container image; let’s look at how to do it.
The cleanest approach is a multi-stage Dockerfile: One stage compiles the binary with the full Go toolchain, and a second, minimal stage just delivers the finished binary.
# ---- Stage 1: build the custom collector with OCB ----
FROM golang:1.23-bookworm AS builder
ARG OCB_VERSION=0.156.0
WORKDIR /build
# Install the builder pinned to the same release train as the manifest
RUN go install go.opentelemetry.io/collector/cmd/builder@v${OCB_VERSION}
# Bring in the manifest and build
COPY manifest.yaml .
RUN builder --config manifest.yaml
# ---- Stage 2: minimal runtime image ----
FROM alpine:3.20
# TLS roots so the collector can reach Elasticsearch over HTTPS
RUN apk add --no-cache ca-certificates
# Copy only the compiled binary from the builder stage
COPY --from=builder /build/otelcol-neteye/otelcol-neteye /otelcol-neteye
# OTLP gRPC and HTTP receiver ports
EXPOSE 4317 4318
ENTRYPOINT ["/otelcol-neteye"]
CMD ["--config", "/etc/otelcol/config.yaml"]
Build it and run it!
The OpenTelemetry Collector Builder is useful whenever an existing distribution is close to what you need, but is missing one or two important components.
Instead of choosing between several incomplete distributions or operating multiple collectors, you can create a focused binary that contains exactly the receivers, processors, connectors, exporters, extensions, and providers required by your deployment.
The same approach works beyond OpenShift and Elastic. Change the receivers, processors, or exporters to match your environment, keep the manifest limited to the components you need, and update the dependency versions as a tested set.
Did you find this article interesting? Does it match your skill set? Our customers often present us with problems that need customized solutions. In fact, we’re currently hiring for roles just like this and others here at Würth IT Italy.