Last updated: September 7, 2026
Set Up SignalControl Edge Without Kubernetes
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 isghcr.io/dash0hq/signal-control-edge-collectorand the edge proxy isghcr.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.
| Region | OTLP ingress (gRPC) | Sampling upstream | API (settings) |
|---|---|---|---|
| EU Central (Frankfurt) | ingress.eu-central-1.aws.dash0.com:4317 | decision-maker.eu-central-1.aws.dash0.com:443 | https://api.eu-central-1.aws.dash0.com |
| EU West (Ireland) | ingress.eu-west-1.aws.dash0.com:4317 | decision-maker.eu-west-1.aws.dash0.com:443 | https://api.eu-west-1.aws.dash0.com |
| US West (Oregon) | ingress.us-west-2.aws.dash0.com:4317 | decision-maker.us-west-2.aws.dash0.com:443 | https://api.us-west-2.aws.dash0.com |
| EU West (GCP) | ingress.europe-west4.gcp.dash0.com:4317 | decision-maker.europe-west4.gcp.dash0.com:443 | https://api.europe-west4.gcp.dash0.com |
Steps
The steps below use EU Central endpoints. Substitute your region's row from the table above.
-
Export the environment variables the collector configuration and the commands below read. Use
exportso that Docker can pass them into the containers. An unset variable expands to the empty string, which fails the collector's configuration validation:sh123export DASH0_AUTH_TOKEN="auth_..."export DASH0_DATASET="default"export DASH0_API="https://api.eu-central-1.aws.dash0.com" -
Start the edge proxy. This is optional but recommended at scale. Skip it to run in direct mode:
sh12345docker 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> -
Save the reference collector configuration below as
config.yamland replace its three placeholders:<dash0 api endpoint for your region>: The API column of the endpoint table, for examplehttps://api.eu-central-1.aws.dash0.com.<dash0 ingress>: The host part of the OTLP ingress column, for exampleingress.eu-central-1.aws.dash0.com.<edge-proxy host:port>:host.docker.internal:8011when the edge proxy from step 2 runs on the same machine. Also setdecision_maker_insecure: true, because the local proxy speaks plaintext gRPC. In direct mode, use the sampling upstream column instead, for exampledecision-maker.eu-central-1.aws.dash0.com:443, and keepdecision_maker_insecure: false.
-
Start the collector with the configuration mounted at
/etc/otelcol/config.yaml:sh12345docker 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 withoutWARNlines from the metering validator. -
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:
sh123456789101112curl -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.
-
Send a test trace with one error span to the collector's OTLP/HTTP port:
sh12345678910111213141516171819NOW="$(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. -
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 thekeep-errorsrule 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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209extensions:# 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_intervalreceivers:otlp:protocols:grpc:endpoint: 0.0.0.0:4317http: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/meteringprocessors:memory_limiter:check_interval: 1slimit_percentage: 80spike_limit_percentage: 20batch: { } # 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 cacheddash0sampling: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 proxyorganization_mode: single # single (one org, from the token) or multidefault_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 latencymax_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/meteringmode: 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/meteringmode: mark_onlydash0metering/logs:metric_recorder: dash0metricrecorder/meteringmode: count_and_markdash0metering/metrics:metric_recorder: dash0metricrecorder/meteringmode: count_and_markconnectors: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 countdash0redmetrics:# 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: 160exporters:otlp/dash0:endpoint: "<dash0 ingress>:4317"headers:Authorization: "Bearer ${env:DASH0_AUTH_TOKEN}"Dash0-Dataset: "${env:DASH0_DATASET}"service:extensions:- dash0settingsonedgeextension- dash0metricrecorder/meteringpipelines:# 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:- otlpprocessors:- memory_limiter- dash0resource- dash0operation- dash0metering/pre-sampling- dash0filterexporters:- dash0signaltometrics- dash0redmetrics- forward/sampling# Metering sits immediately before dash0sampling, the first Dash0 capability here.traces/sampling:receivers:- forward/samplingprocessors:- dash0metering/sampling- dash0sampling- batchexporters:- 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:- otlpprocessors:- memory_limiter- dash0resource- dash0metering/logs- dash0filter- batchexporters:- dash0signaltometrics- otlp/dash0# Metrics: spam filtering only. Time series aggregation runs in Dash0, not at the edge.metrics:receivers:- otlpprocessors:- memory_limiter- dash0resource- dash0operation- dash0metering/metrics- dash0filter- batchexporters:- 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/meteringexporters:- 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:
12345extensions:dash0settingsonedgeextension:endpoint: "https://api.<region>.aws.dash0.com" # requiredauth_token: ${env:DASH0_AUTH_TOKEN} # required; org is implicit in the tokenrefresh_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:
12345extensions: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>:4317withAuthorization: Bearer <token>and aDash0-Datasetheader. - Sampling:
dash0sampling.decision_maker_endpointpoints at the edge proxy, or the Dash0 sampling upstream in direct mode.decision_maker_headerscarry the bearer token andDash0-Dataset, required wheneverorganization_modeissingle(the default). Usedecision_maker_insecure: trueonly for a plaintext local edge proxy.
Further Reading
- About SignalControl Edge. The architecture, collector components, and limits.
- Deploy the Edge Proxy. Configure the proxy that fans settings and sampling out to your collectors.
- Sample Traces. Author tail-sampling rules and tune the reservoir.