# Creating Your Own Collector Distribution from OllyGarden Tulip

Tulip gives you a commercially supported OpenTelemetry Collector with a curated component set. When you need more, you can derive your own distribution while keeping support for the core pipeline. This post walks through how.

Published: 2026-05-27
Canonical URL: https://ollygarden.com/resources/blog/creating-your-own-collector-distribution-from-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](https://github.com/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 `batchprocessor` was 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 via `sending_queue` and `batch` settings, 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`, not `otlp`?** As of otelcol 0.151.0 the OTLP exporter is registered as `otlp_grpc` (and the HTTP variant as `otlp_http`). The old `otlp`/`otlphttp` names still resolve as deprecated aliases, but new configurations should use the explicit names. Note the OTLP *receiver* is unaffected — it's still `otlp`.

## 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

```

**Help your coding agent write Collector config.** OllyGarden's free, open source OpenTelemetry Agent Skills give Claude Code, Cursor, and other coding agents current Collector configuration knowledge, so the manifests and pipelines they draft draw on upstream documentation. [Get the skills](https://ollygarden.com/resources/agent-skills)

## 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 `transformprocessor` has 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](https://github.com/ollygarden/tulip) and check out the [OpenTelemetry Collector Contrib repository](https://github.com/open-telemetry/opentelemetry-collector-contrib) for the full component catalog.*