Skip to main contentSkip to navigationSkip to footer
    Back to Blog

    Creating Your Own Collector Distribution from OllyGarden Tulip

    Juraci Paixão Kröhling
    opentelemetryopentelemetry-collectortulip
    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 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 installgo 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
    

    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:

    1. Clone Tulip's manifest structure
    2. Add your required components (keeping versions aligned)
    3. Build with ocb
    4. Containerize and deploy
    5. 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.

    We use analytics cookies to improve our website and understand how you use it. You can accept analytics cookies or decline to use only essential cookies.