Dash0 acquires Polar Signals

Last updated: September 7, 2026

Set Up SignalControl Edge Without Kubernetes

Run the SignalControl Edge collector image with Docker, including region endpoints, the reference collector configuration, and a smoke test.

This page covers running SignalControl Edge without Kubernetes: the Dash0 edge collector image with Docker, the optional edge proxy, and the reference collector configuration. For the Kubernetes deployment, see Dash0 SignalControl Edge instead.

Dash0 ships SignalControl Edge as container images only. The examples use Docker, but any container runtime works.

Prerequisites

  • Docker: Or another container runtime. The smoke test at the end also uses curl.
  • A Dash0 auth token: An organization token (auth_…) with ingest and read permission. Creating sampling rules through the API additionally requires the admin role. See Auth Tokens.
  • A Dash0 dataset: The examples use default.
  • The Dash0 container images: Both are published to GitHub Container Registry and released together under the same version tag, for example 1.1.0. The collector is ghcr.io/dash0hq/signal-control-edge-collector and the edge proxy is ghcr.io/dash0hq/edge-proxy. The examples below use <tag> as a placeholder for the release you pick.

Endpoints

This is the canonical endpoint table for SignalControl Edge. Pick the row for your region.

RegionOTLP ingress (gRPC)Sampling upstreamAPI (settings)
EU Central (Frankfurt)ingress.eu-central-1.aws.dash0.com:4317decision-maker.eu-central-1.aws.dash0.com:443https://api.eu-central-1.aws.dash0.com
EU West (Ireland)ingress.eu-west-1.aws.dash0.com:4317decision-maker.eu-west-1.aws.dash0.com:443https://api.eu-west-1.aws.dash0.com
US West (Oregon)ingress.us-west-2.aws.dash0.com:4317decision-maker.us-west-2.aws.dash0.com:443https://api.us-west-2.aws.dash0.com
EU West (GCP)ingress.europe-west4.gcp.dash0.com:4317decision-maker.europe-west4.gcp.dash0.com:443https://api.europe-west4.gcp.dash0.com

Steps

The steps below use EU Central endpoints. Substitute your region's row from the table above.

  1. Export the environment variables the collector configuration and the commands below read. Use export so that Docker can pass them into the containers. An unset variable expands to the empty string, which fails the collector's configuration validation:

    sh
    123
    export DASH0_AUTH_TOKEN="auth_..."
    export DASH0_DATASET="default"
    export DASH0_API="https://api.eu-central-1.aws.dash0.com"
  2. Start the edge proxy. This is optional but recommended at scale. Skip it to run in direct mode:

    sh
    12345
    docker run --rm --detach --name edge-proxy --publish 8011:8011 --publish 8012:8012 \
    -e UPSTREAM_ADDRESS=decision-maker.eu-central-1.aws.dash0.com:443 \
    -e UPSTREAM_HEADERS="authorization=Bearer ${DASH0_AUTH_TOKEN},Dash0-Dataset=${DASH0_DATASET}" \
    -e LISTENADDRESS=:8011 -e LISTENADDRESSINTERNAL=:8012 \
    ghcr.io/dash0hq/edge-proxy:<tag>
  3. Save the reference collector configuration below as config.yaml and replace its three placeholders:

    • <dash0 api endpoint for your region>: The API column of the endpoint table, for example https://api.eu-central-1.aws.dash0.com.
    • <dash0 ingress>: The host part of the OTLP ingress column, for example ingress.eu-central-1.aws.dash0.com.
    • <edge-proxy host:port>: host.docker.internal:8011 when the edge proxy from step 2 runs on the same machine. Also set decision_maker_insecure: true, because the local proxy speaks plaintext gRPC. In direct mode, use the sampling upstream column instead, for example decision-maker.eu-central-1.aws.dash0.com:443, and keep decision_maker_insecure: false.
  4. Start the collector with the configuration mounted at /etc/otelcol/config.yaml:

    sh
    12345
    docker run --rm --detach --name edge-collector --publish 4317:4317 --publish 4318:4318 \
    --add-host=host.docker.internal:host-gateway \
    -v "$(pwd)/config.yaml:/etc/otelcol/config.yaml" \
    -e DASH0_AUTH_TOKEN -e DASH0_DATASET \
    ghcr.io/dash0hq/signal-control-edge-collector:<tag>

    Read the first few seconds of the log with docker logs edge-collector. A correct configuration starts without WARN lines from the metering validator.

  5. Create a sampling rule. Without any rule, sampling is off for the dataset and every trace passes through, so the test would not exercise tail sampling. Once a rule exists, only traces matching a rule are kept. The rule below keeps every trace that contains an error span:

    sh
    123456789101112
    curl -X POST "${DASH0_API}/api/sampling-rules?dataset=${DASH0_DATASET}" \
    -H "Authorization: Bearer ${DASH0_AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
    "kind": "Dash0Sampling",
    "metadata": { "name": "keep-errors" },
    "spec": {
    "enabled": true,
    "display": { "name": "Keep error traces" },
    "conditions": { "kind": "error", "spec": {} }
    }
    }'

    The rule reaches the collector through its sampling connection, directly from the decision maker or via the edge proxy. Allow about a minute before sending test data.

  6. Send a test trace with one error span to the collector's OTLP/HTTP port:

    sh
    12345678910111213141516171819
    NOW="$(date +%s)000000000"
    curl -X POST http://localhost:4318/v1/traces \
    -H "Content-Type: application/json" \
    -d "{
    \"resourceSpans\": [{
    \"resource\": { \"attributes\": [
    { \"key\": \"service.name\", \"value\": { \"stringValue\": \"signalcontrol-smoke-test\" } }
    ] },
    \"scopeSpans\": [{ \"spans\": [{
    \"traceId\": \"5b8aa5a2d2c872e8321cf37308d69df2\",
    \"spanId\": \"051581bf3cb55c13\",
    \"name\": \"GET /smoke-test\",
    \"kind\": 2,
    \"startTimeUnixNano\": \"${NOW}\",
    \"endTimeUnixNano\": \"${NOW}\",
    \"status\": { \"code\": 2, \"message\": \"smoke test\" }
    }] }]
    }]
    }"

    An empty JSON object ({}) in the response means the collector accepted the span.

  7. Confirm the trace arrived. In Dash0, open Tracing and filter for the service signalcontrol-smoke-test. The trace can take up to the reservoir's buffer duration, one minute in the reference configuration, to appear. Then delete the keep-errors rule if you do not want to keep it. Note that removing the last rule turns sampling off again for the dataset.

Reference Collector Configuration

A complete, runnable edge collector: OTLP in for traces, logs, and metrics; tail sampling, RED metrics, and signal-to-metrics for traces; signal-to-metrics for logs; spam filtering for all three signals; and metering wired for all of it. Placeholders in angle brackets are environment-specific, and two environment variables must be set for it to load: DASH0_AUTH_TOKEN and DASH0_DATASET. Optional settings are shown commented out with their defaults, so you can see what is available to tune. The capability pages explain each one. You only need the uncommented lines to run.

yaml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
extensions:
# Tells every Dash0 component this collector runs at the edge, and supplies the settings
# (signal-to-metrics rules, filter rules, dataset settings) the capabilities need.
dash0settingsonedgeextension:
# Direct HTTP: poll Dash0 directly. To use the edge proxy instead, remove these fields and set `proxy:` below.
endpoint: "<dash0 api endpoint for your region>"
auth_token: ${env:DASH0_AUTH_TOKEN}
refresh_interval: 60s # settings poll period; 10s–1h
# proxy: # Edge proxy gRPC: subscribe to a local edge proxy instead of polling Dash0 directly
# address: "edge-proxy-host:8011"
# insecure: false # true only for a plaintext local edge proxy
# Holds the usage counters the metering processors record, until the paired receiver below
# drains them into a metrics pipeline.
dash0metricrecorder/metering:
flush_interval: 1m # how often counters flush to the drain pipeline
# queue_size: 1000 # in-memory queue of pending metric batches
# max_stale: 5m # drop a per-tenant recorder after this idle; must be > flush_interval
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# The drain side of the recorder. Its ID must name the extension instance above.
dash0metricrecorder/metering:
metric_recorder: dash0metricrecorder/metering
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
batch: { } # optional: send_batch_size, timeout, send_batch_max_size
# Enrichment. Not SignalControl capabilities, and never a reason to configure metering.
# dash0resource handles traces, logs, and metrics. dash0operation handles traces and metrics.
dash0resource: { }
dash0operation: { }
# Telemetry filtering. Drops telemetry matching your per-dataset filter rules. The same
# processor instance serves the traces, logs, and metrics pipelines below.
dash0filter:
apply_positive_filters_only: true # recommended on edge: skip negative-operator rules so an over-broad rule cannot drop everything
# cache_expiration: 60s # how long compiled filter rules are cached
dash0sampling:
decision_maker_endpoint: "<edge-proxy host:port>" # edge proxy, or the Dash0 sampling upstream in direct mode
# Required whenever organization_mode is single (the default): the headers authenticate the
# organization for rule lookup, so the processor refuses to start without them.
decision_maker_headers:
Authorization: "Bearer ${env:DASH0_AUTH_TOKEN}"
Dash0-Dataset: "${env:DASH0_DATASET}"
decision_maker_insecure: false # true only for a plaintext local edge proxy
organization_mode: single # single (one org, from the token) or multi
default_dataset: "${env:DASH0_DATASET}" # dataset used when a span carries none
# enable_batching: false # batch satisfaction reports to the upstream
# max_batch_size: 500 # only when enable_batching is true
# max_batch_wait: 200ms # only when enable_batching is true
# decision_channel_max_size: 10000 # buffer for incoming decisions
# eviction_channel_size: 1000 # buffer for spans leaving the reservoir
# fallback_sample_ratio: 0.01 # keep ratio when the upstream is unreachable / rules not yet delivered
# fallback_min_connected_ratio: 1.0 # fall back when connected upstreams drop below this fraction
# fallback_until_rules_received: true # use the fallback ratio at startup until rules arrive
# debug: false # verbose per-decision logging (high volume)
reservoir:
type: serialized_memory # serialized_memory (recommended) | memory | disk (the default when unset)
buffer_duration: 60s # how long spans wait for a decision; must cover decision latency
max_memory_bytes: 104857600 # 100 MB; used by memory and serialized_memory
# shard_count: 16 # write parallelism; disk, serialized_memory, memory (memory defaults to CPU count)
# ingest_channel_size: 100 # per-shard ingest buffer
# eviction_scan_send_timeout: 1s
# --- memory type only ---
# estimated_bytes_per_span: 800 # size estimate used for the memory calculation
# --- disk type only ---
# data_dir: /var/lib/dash0/reservoir # required for type: disk; use fast SSD/NVMe
# max_disk_bytes: 0 # unlimited (age-based eviction only); set >0 to cap disk, e.g. 1073741824 (1 GiB)
# rotation_interval: 5s # how often active files are sealed
# writer_buffer_size: 262144 # 256 KB per-shard write buffer
# Metering. Exactly one count_and_mark instance per source and signal type. The otlp receiver
# fans out per signal type, so traces, logs, and metrics each get their own counting instance
# and every signal is counted exactly once.
dash0metering/pre-sampling:
metric_recorder: dash0metricrecorder/metering
mode: count_and_mark # count_and_mark | mark_only | count (count is invalid on edge)
# old_data_threshold: 24h # do not count signals older than this; 0 disables the check
# web_spans_processing_enabled: false # set true on a pipeline that processes web-event spans
# Downstream of the traces counter in the same chain, so the mark keeps these spans from being
# counted twice. It still marks, so dash0sampling below keeps acting once your organization is enforcing.
dash0metering/sampling:
metric_recorder: dash0metricrecorder/metering
mode: mark_only
dash0metering/logs:
metric_recorder: dash0metricrecorder/metering
mode: count_and_mark
dash0metering/metrics:
metric_recorder: dash0metricrecorder/metering
mode: count_and_mark
connectors:
forward/sampling: { }
dash0signaltometrics:
# default_dataset: default
# metrics_flush_interval: 60s # how often generated metrics are exported
# cache_expiration: 60s # how long the compiled ruleset is cached
# max_time_series: 30000 # in-memory series cap
# histogram:
# exponential:
# max_size: 160 # exponential-histogram bucket count
dash0redmetrics:
# metrics_flush_interval: 60s
# max_time_series: 5000 # soft in-memory series cap
# max_time_series_age: 10m # age at which idle series are cleaned up
# min_time_series_age: 30s # minimum age before cleanup
# additional_span_attributes: [] # extra span attributes to add as metric dimensions
# cardinality_threshold: 3 # max distinct values per additional attribute (1–3)
# histogram:
# exponential:
# max_size: 160
exporters:
otlp/dash0:
endpoint: "<dash0 ingress>:4317"
headers:
Authorization: "Bearer ${env:DASH0_AUTH_TOKEN}"
Dash0-Dataset: "${env:DASH0_DATASET}"
service:
extensions:
- dash0settingsonedgeextension
- dash0metricrecorder/metering
pipelines:
# dash0metering/pre-sampling sits just before dash0filter, the first Dash0 capability here,
# so it counts every span entering filtering, once. dash0signaltometrics and dash0redmetrics
# then see the surviving spans on the exporter side, before sampling.
traces/pre-sampling:
receivers:
- otlp
processors:
- memory_limiter
- dash0resource
- dash0operation
- dash0metering/pre-sampling
- dash0filter
exporters:
- dash0signaltometrics
- dash0redmetrics
- forward/sampling
# Metering sits immediately before dash0sampling, the first Dash0 capability here.
traces/sampling:
receivers:
- forward/sampling
processors:
- dash0metering/sampling
- dash0sampling
- batch
exporters:
- otlp/dash0
# Logs: spam filtering, and signal-to-metrics rules that match logs. Metering sits just
# before dash0filter, the first Dash0 capability in this pipeline.
logs:
receivers:
- otlp
processors:
- memory_limiter
- dash0resource
- dash0metering/logs
- dash0filter
- batch
exporters:
- dash0signaltometrics
- otlp/dash0
# Metrics: spam filtering only. Time series aggregation runs in Dash0, not at the edge.
metrics:
receivers:
- otlp
processors:
- memory_limiter
- dash0resource
- dash0operation
- dash0metering/metrics
- dash0filter
- batch
exporters:
- otlp/dash0
# Needs no metering: dash0signaltometrics and dash0redmetrics appear in receivers, and
# their metrics were already counted upstream as the signals they were derived from. The
# usage counters ride out here too, because a pipeline fed by a Dash0 connector reaches
# Dash0 by construction.
metrics/signal-control:
receivers:
- dash0signaltometrics
- dash0redmetrics
- dash0metricrecorder/metering
exporters:
- otlp/dash0

This does not meter any signal twice. For traces, only dash0metering/pre-sampling counts (count_and_mark); dash0metering/sampling is mark_only and never adds to your usage. Logs and metrics reach the collector as separate signal types, so their counting instances see disjoint data.

Authentication and Connecting Back to Dash0

The settings extension picks its connect-back path by whether proxy.address is set.

Direct HTTP. The extension polls the edge settings endpoint directly:

yaml
12345
extensions:
dash0settingsonedgeextension:
endpoint: "https://api.<region>.aws.dash0.com" # required
auth_token: ${env:DASH0_AUTH_TOKEN} # required; org is implicit in the token
refresh_interval: 60s # 10s–1h

Edge proxy gRPC. The extension subscribes to a local edge proxy. The endpoint, auth_token, and refresh_interval fields are accepted but ignored:

yaml
12345
extensions:
dash0settingsonedgeextension:
proxy:
address: "edge-proxy-host:8011"
insecure: false # true only for local and plaintext

Other connect-back paths:

  • Telemetry export: A standard OTLP exporter to <dash0 ingress>:4317 with Authorization: Bearer <token> and a Dash0-Dataset header.
  • Sampling: dash0sampling.decision_maker_endpoint points at the edge proxy, or the Dash0 sampling upstream in direct mode. decision_maker_headers carry the bearer token and Dash0-Dataset, required whenever organization_mode is single (the default). Use decision_maker_insecure: true only for a plaintext local edge proxy.

Further Reading