Docker Compose
Compose reads a .env file next to the compose file automatically, and
interpolates ${...} into the YAML. That gives you one place for the two
variables and no key in the committed file.
The shape
.env, which is in .gitignore:
NEXOVUE_ENDPOINT=https://ingest.nexovue.com
NEXOVUE_KEY=nvk_live_xxxxxxxxxxxxxxxxxxxxxx
compose.yaml, which is committed:
x-nexovue: &nexovue
OTEL_EXPORTER_OTLP_ENDPOINT: ${NEXOVUE_ENDPOINT:?set NEXOVUE_ENDPOINT in .env}
OTEL_EXPORTER_OTLP_HEADERS: x-nexovue-key=${NEXOVUE_KEY:?set NEXOVUE_KEY in .env}
OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf
services:
checkout:
image: checkout:1.4.0
environment:
<<: *nexovue
OTEL_SERVICE_NAME: checkout
inventory:
image: inventory:2.1.0
environment:
<<: *nexovue
OTEL_SERVICE_NAME: inventory
Three things about this that are deliberate.
The anchor. x-nexovue: &nexovue is a Compose extension field — Compose
ignores keys starting x- — and <<: *nexovue merges it into each service.
Three services means three merges, not three copies to keep in step.
OTEL_SERVICE_NAME per service, never in the anchor. It is the one value
that must differ. Put it in the shared block and every container reports as the
same service, the service list has one row, and the service map has one node.
:?message. Compose fails to start rather than interpolating an empty
string. Without it, a missing .env produces containers configured with an empty
endpoint, which fail silently for as long as nobody looks.
Adding it to an existing compose file
If you would rather not restructure, env_file on each service is the smaller
change:
services:
checkout:
env_file: [nexovue.env]
environment:
OTEL_SERVICE_NAME: checkout
nexovue.env holds the three lines verbatim, with no quoting and no ${}:
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.nexovue.com
OTEL_EXPORTER_OTLP_HEADERS=x-nexovue-key=nvk_live_xxxxxxxxxxxxxxxxxxxxxx
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
env_file values are passed through literally; environment values are
interpolated by Compose first. That difference is why a $ in a value behaves
differently between the two, and why the file above needs no escaping.
Pointing at a local nexovue
Containers on a Compose network reach your host through host.docker.internal
on Docker Desktop:
NEXOVUE_ENDPOINT=http://host.docker.internal:14318
On plain Linux Docker that name does not exist unless you add it:
services:
checkout:
extra_hosts:
- "host.docker.internal:host-gateway"
localhost inside a container is the container. Pointing an exporter at
http://localhost:14318 from inside one is the most common way to send nothing
and see no error.
Adding a Collector
If you run several services in one Compose project, a Collector between them and
nexovue is worth the extra container: your services then export to
http://collector:4318 with no key at all, and only the Collector holds the
credential.
services:
collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/collector.yaml"]
volumes:
- ./collector.yaml:/etc/collector.yaml:ro
environment:
NEXOVUE_ENDPOINT: ${NEXOVUE_ENDPOINT:?}
NEXOVUE_KEY: ${NEXOVUE_KEY:?}
checkout:
environment:
OTEL_EXPORTER_OTLP_ENDPOINT: http://collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf
OTEL_SERVICE_NAME: checkout
Pin the Collector image to a released version rather than latest before this
reaches anything you care about. collector.md has the
Collector's own configuration and the reasons you might want one.