Skip to main content
If your application already runs under Docker Compose, adding telemetry is one service and one config file. Compose gives you the piece that is fiddly by hand: a stable DNS name every other service can export to.
Before you start, you need two values.
  1. Your base endpoint — shown on Get Started in the dashboard. These pages write it as $OIQ_ENDPOINT.
  2. A license key — created in Settings → License Keys, starting oiq_. Requires the Admin role. See Create a license key. These pages write it as $OIQ_LICENSE_KEY.
Export both before running anything below:
You also need an existing docker-compose.yml and Docker Compose v2 — docker compose version should succeed. Note the space; docker-compose v1 is end-of-life and its env_file handling differs.

Why a service rather than a sidecar per container

One collector serves the whole stack. Compose puts every service on a shared network with DNS, so http://collector:4318 resolves from any container without publishing a single port to the host. That matters more than the convenience: your application services never receive the license key. Only the collector does. When you rotate the key, you change one service definition instead of auditing every container’s environment.

1. Write the config

Save this next to your docker-compose.yml.
config.yaml

2. Add the collector service

docker-compose.yml
The :? in ${OIQ_ENDPOINT:?…} is doing real work. Without it, an unset variable becomes an empty string and Compose starts a collector that exports to nowhere, logging retries you have to go looking for. With it, docker compose up refuses to start and tells you which variable is missing.
Add the config to root_path so the mounted host filesystem is used:
config.yaml

3. Provide the two values

Compose reads a .env file beside docker-compose.yml automatically:
.env
Add .env to .gitignore. A license key is an ingest credential for your whole organization, and it is not scoped to a service or an environment — so a key committed to a repository is a key that has to be revoked, not rotated at leisure.

4. Start it

Expected output — the last line is the success signal:
Then bring up the rest of the stack:

Verify

The collector’s metrics port is not published, so read it from inside the Compose network rather than from your shell:
Expected output:
send_failed at zero with sent climbing is a working stack. Then open Logs in the product, set the range to the last 15 minutes, and search for one of your service names.

Troubleshooting

Working as intended — the :? guard caught an unset variable before starting a collector that would have exported nowhere.Confirm .env sits beside docker-compose.yml, not beside config.yaml if those are different directories. Compose looks next to the Compose file, or wherever --env-file points.
Compose DNS uses the service key, not container_name. In the file above the service is collector, so the name is http://collector:4318 even though the container is called aiaxoniq-collector.Services in different Compose projects are on different networks and cannot see each other without an explicit shared external network.
depends_on waits for the container to start, not for it to be listening. In practice this is harmless: OpenTelemetry SDKs buffer and retry, so early spans are not lost — they arrive once the collector accepts connections.If early data genuinely matters, add a healthcheck and depends_on: {collector: {condition: service_healthy}}.
Read the error field on that line — it names the cause exactly.
  • no such host — the endpoint hostname does not resolve. Check $OIQ_ENDPOINT against the value on Get Started.
  • connection refused — the host resolves but nothing is listening on that port.
  • 401 Unauthorized — the key is missing, malformed or revoked.
  • 404 — the endpoint already ends in /v1/…. It must be the base URL; the collector appends the signal path itself.
The collector retries with backoff and does not drop data while it retries, so a transient failure here is not a loss.
The collector is running and receiving nothing. That is an application-side problem, not a collector one — your services are not exporting to it.Check that your application’s OTEL_EXPORTER_OTLP_ENDPOINT points at the collector’s OTLP port (4318 for HTTP, 4317 for gRPC), not at aiAxonIQ.
Two causes that are not about the key’s value:
  • A trailing newline. A key read from a file created by a shell heredoc, or a Kubernetes Secret made with --from-file, carries the newline as part of the value. Use --from-literal, or printf rather than echo.
  • A validation outage. If the receiver cannot reach the service that validates keys it fails closed and returns the same 401. A sudden 401 across every service at once, with a key you have not changed, is far more likely to be this. Check $OIQ_ENDPOINT/health first.
The collector reads its configuration only at startup. Restart it after any edit, and confirm the file you edited is the one mounted into the process — a bind mount pointing at a path that does not exist silently yields the image’s default config rather than an error.

Next

Instrument your application

Zero-code setup per language.

Kubernetes

When the stack moves to a cluster.

Verify your data

Confirm it landed.