Creating Your Own Collector Distribution from OllyGarden Tulip

One of the most common complaints I hear from platform teams adopting OpenTelemetry: "We love the vendor neutrality, but we can't run a critical piece of infrastructure without support."
The OpenTelemetry Collector is powerful. It lets you decouple your instrumentation from your backend, so switching observability platforms doesn't mean reinstrumenting your entire stack. But for many organizations, running the upstream Collector in production without a support contract is a non-starter.
OllyGarden Tulip solves this. It's a commercially supported distribution of the OpenTelemetry Collector that gives you ready-to-use binaries and container images with a curated set of components — plus a manifest system that lets you derive your own custom builds, mixing supported components with community modules.
You get the vendor neutrality that OpenTelemetry promises, backed by the support your organization requires. And when you need components beyond the curated set, you can extend Tulip while keeping commercial support for the core pipeline.
This post walks through creating your own distribution derived from Tulip, using the v26.05.1 release — the first Long-Term Support (LTS) release — as the baseline.
What's in the Tulip Manifest?
Before extending, understand what you're starting with. Tulip v26.05.1 includes:
Extensions: zpages, pprof, basicauth, bearertokenauth, oauth2client, oidc, filestorage
Receivers: otlp, nop, hostmetrics, filelog
Processors: attributes, resource, span, probabilisticsampler, filter, transform, redaction
Exporters: otlp, otlphttp, file, debug, nop
Connectors: forward
This is a deliberately focused set covering the most common telemetry pipeline patterns. If you need components beyond this list — say, the Kafka receiver, Prometheus exporter, or tail sampling processor — you'll add them to your fork. One thing to keep in mind: commercial support covers only the components in the Tulip manifest. Anything you add yourself is yours to maintain. We cover exactly where that line sits in the "Understanding Support Boundaries" section below.
No batch processor? That's intentional. The
batchprocessorwas removed in the LTS May 2026 release: it has been deprecated upstream and carries a known data-loss bug. Instead, batching now happens at the exporter level viasending_queueandbatchsettings, which is more robust under backpressure. You'll see this reflected in the configuration examples below.
Step 1: Clone the Tulip Repository Structure
Start by creating your distribution directory. Tulip uses a multi-distribution repository structure:
mkdir -p my-collector/distributions/my-distro
cd my-collector
Create the essential files:
distributions/my-distro/
├── manifest.yaml # Component manifest (the important one)
├── config.yaml # Default runtime configuration
├── Dockerfile # Container image
├── my-distro.service # systemd service file
├── my-distro.conf # systemd environment file
└── my-distro-test.yaml # Test configuration
Step 2: Create Your Manifest
The manifest is the heart of your distribution. It declares which components to include and their versions.
Here's a manifest that extends Tulip with the Kafka receiver and tail sampling processor:
# distributions/my-distro/manifest.yaml
dist:
module: github.com/my-org/my-collector/my-distro
name: my-distro
description: Custom collector with Kafka and tail sampling
output_path: ./_build
version: 0.151.0
build_tags: "grpcnotrace"
extensions:
# Keep Tulip's extensions
- gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/oidcauthextension v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/filestorage v0.151.0
receivers:
- gomod: go.opentelemetry.io/collector/receiver/nopreceiver v0.151.0
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.151.0
# ADD: Kafka receiver
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.151.0
processors:
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/redactionprocessor v0.151.0
# ADD: Tail sampling processor
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.151.0
exporters:
- gomod: go.opentelemetry.io/collector/exporter/nopexporter v0.151.0
- gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.151.0
- gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.151.0
- gomod: go.opentelemetry.io/collector/exporter/otlphttpexporter v0.151.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.151.0
connectors:
- gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.151.0
providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v1.57.0
- gomod: go.opentelemetry.io/collector/confmap/provider/fileprovider v1.57.0
- gomod: go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.57.0
Version Alignment Matters
Notice that all opentelemetry-collector-contrib components use the same version (v0.151.0). Keeping them aligned matters because mixing incompatible core and contrib versions leads to dependency conflicts and runtime panics.
For the components you inherit from Tulip, use the versions in the Tulip manifest — that's the exact combination OllyGarden tests and supports. For components you add yourself, we strongly recommend matching that same version so the whole build stays consistent, but it isn't enforced: anything beyond the manifest is yours to keep compatible.
The version mapping for Tulip v26.05.1:
- Collector core / contrib components:
v0.151.0 - Confmap providers:
v1.57.0(these follow their own semver line)
When upgrading, update all core and contrib versions together, and bump the confmap providers to their matching release.
Step 3: Build Your Distribution
Install the OpenTelemetry Collector Builder (ocb). For the v0.151.0 release, download the released binary rather than using go install — go install go.opentelemetry.io/collector/cmd/builder@v0.151.0 resolves to the parent go.opentelemetry.io/collector module (which no longer carries the builder) and fails for this specific tag. Grab the prebuilt ocb instead, adjusting the asset name for your OS and architecture:
curl --proto '=https' --tlsv1.2 -fL -o ocb \
https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/cmd%2Fbuilder%2Fv0.151.0/ocb_0.151.0_linux_amd64
chmod +x ocb
sudo mv ocb /usr/local/bin/ocb
Generate and build:
cd distributions/my-distro
# Generate Go source code from manifest
ocb --config manifest.yaml
# Build a static binary (CGO_ENABLED=0) so it runs on the scratch-based image
cd _build
CGO_ENABLED=0 go build -o ../bin/my-distro .
The builder creates a _build/ directory with generated Go code. Don't edit these files directly — regenerate from the manifest instead.
Step 4: Create a Default Configuration
Your distribution needs a sensible default configuration. Note that batching is configured on the exporter (sending_queue + batch), not as a pipeline processor:
# distributions/my-distro/config.yaml
extensions:
pprof: {}
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
kafka:
brokers:
- ${env:KAFKA_BROKERS}
traces:
topics: [otlp_spans]
encoding: otlp_proto
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code:
status_codes: [ERROR]
- name: slow-traces
type: latency
latency:
threshold_ms: 1000
- name: probabilistic
type: probabilistic
probabilistic:
sampling_percentage: 10
exporters:
otlp_grpc:
endpoint: ${env:OTLP_ENDPOINT}
tls:
insecure: ${env:OTLP_INSECURE:-false}
sending_queue:
enabled: true
queue_size: 1000
batch:
flush_timeout: 200ms
min_size: 8192
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
service:
extensions: [pprof]
pipelines:
traces:
receivers: [otlp, kafka]
processors: [tail_sampling]
exporters: [otlp_grpc]
otlp_grpc, nototlp? As of otelcol 0.151.0 the OTLP exporter is registered asotlp_grpc(and the HTTP variant asotlp_http). The oldotlp/otlphttpnames still resolve as deprecated aliases, but new configurations should use the explicit names. Note the OTLP receiver is unaffected — it's stillotlp.
Step 5: Containerize
Create a Dockerfile for your distribution:
# distributions/my-distro/Dockerfile
FROM alpine:3.21 AS certs
RUN apk add --no-cache ca-certificates
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --chmod=755 bin/my-distro /my-distro
COPY config.yaml /etc/my-distro/config.yaml
USER 10001:10001
EXPOSE 4317 4318
ENTRYPOINT ["/my-distro"]
CMD ["--config=/etc/my-distro/config.yaml"]
Build and run:
docker build -t my-distro:latest distributions/my-distro/
docker run -p 4317:4317 -p 4318:4318 my-distro:latest
The build context is distributions/my-distro/, so the COPY bin/my-distro and COPY config.yaml paths in the Dockerfile resolve relative to that directory — exactly where Step 3 wrote the binary.
Step 6: Test Your Distribution
Create a test configuration that exercises your added components:
# distributions/my-distro/my-distro-test.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: localhost:4317
processors:
tail_sampling:
decision_wait: 1s
policies:
- name: test-policy
type: always_sample
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling]
exporters: [debug]
Run a smoke test:
# Start the collector
./bin/my-distro --config my-distro-test.yaml &
# Send test traces
telemetrygen traces --otlp-insecure --traces 5
# Verify output shows the trace
# Then stop the collector
kill %1
Understanding Support Boundaries
When you derive from Tulip, support scope follows a clear hierarchy:
| Component | Support Level |
|---|---|
| Tulip manifest components | Full commercial support |
| Contrib components you add | Best-effort guidance |
| Custom/private components | Your responsibility |
This means:
- If
transformprocessorhas a bug, OllyGarden will triage and fix it - If
kafkareceiver(which you added) has issues, OllyGarden provides integration tips but won't ship patches - If you add a custom receiver from your own codebase, that's entirely on you
This model lets you extend without losing the value of commercial support for the core pipeline.
Best Practices for Maintaining Your Fork
Pin to Tulip Release Versions
Don't chase upstream head. Align your distribution versions with Tulip releases:
# Good: Match Tulip v26.05.1
version: 0.151.0
# Bad: A random version that doesn't match a Tulip release
version: 0.153.0
This ensures your base components match a tested, supported configuration.
Document Your Additions
Keep a CHANGELOG noting which components you've added beyond Tulip's manifest:
## Custom Components
### Receivers
- `kafkareceiver` - Consumes traces from Kafka topics
### Processors
- `tailsamplingprocessor` - Intelligent trace sampling
### Rationale
Kafka integration required for our event-driven architecture.
Tail sampling reduces storage costs while preserving error traces.
Separate Configuration from Distribution
Your distribution binary should be configuration-agnostic. Ship a minimal default config, but let operators provide their own via:
./my-distro --config file:/etc/my-distro/config.yaml
When to Consider a Custom Distribution
Deriving from Tulip makes sense when:
- You need specific receivers/exporters not in Tulip's manifest
- You want commercial support for the core pipeline
- Your organization has compliance requirements around supported software
- You're standardizing on a single collector binary across teams
Consider sticking with vanilla Tulip if:
- The included components meet your needs
- You prefer zero maintenance overhead
- You're still evaluating your telemetry architecture
Summary
Creating a custom distribution from Tulip gives you the best of both worlds: a production-tested, commercially supported foundation with the flexibility to add components your organization needs.
The process:
- Clone Tulip's manifest structure
- Add your required components (keeping versions aligned)
- Build with ocb
- Containerize and deploy
- Maintain version alignment with Tulip releases
Your core pipeline remains supported. Your extensions remain flexible. And when issues arise, you know exactly which components fall under commercial support and which are your team's responsibility.
Ready to get started? Clone the Tulip repository and check out the OpenTelemetry Collector Contrib repository for the full component catalog.
