The use of containers has now become part of everyday activities: in many cases we use them as software appliances or black-boxes. Sometimes, however, it happens that these black-boxes do not work as we expect or we would like to be able to evaluate their performance as the load increases.
Today we are going to talk about a real use case, the data collection of one or more Gotenberg containers. Gotenberg is an API that allows you to convert and manipulate documents in PDF format in a way that is easy for developers. It receives files via http (multipart/form-data) requests and returns the processed document without requiring you to manually configure complex dependencies on your server.
One of our customers has been using it for about a year having integrated it into their PHP-based application and as often happens, after an initial testing phase we encountered some performance problems.
Fortunately, in one of the latest releases, the function of exporting performance data via telemetry was implemented and we were able to integrate this data into the APM component of NetEye together with the PHP application data.
Anticipating an increase in load, the customer asked us to implement a new solution consisting of a reverse proxy that would balance the load between three Gotenberg containers. To these we added a container with the function of local otel collector that will take care of forwarding the collected data to the Neteye platform.
x-gotenberg-base: &gotenberg-base
build:
context: ./gotenberg
dockerfile: Dockerfile
image: ${GOTENBERG_IMAGE:-gotenberg:custom}
environment:
- GOTENBERG_CHROMIUM_AUTO_START=true
- GOTENBERG_CHROMIUM_RESTART_AFTER=0
# OTEL CONFIGURATION
- OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- OTEL_TRACES_EXPORTER=otlp
- OTEL_METRICS_EXPORTER=otlp
- OTEL_LOGS_EXPORTER=otlp
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_COLLECTOR_OTLP_HTTP_ENDPOINT:-http://otel-collector:4318}
- OTEL_EXPORTER_OTLP_INSECURE=true
- OTEL_RESOURCE_ATTRIBUTES=service.name=gotenberg,deployment.environment=staging
- OTEL_TRACES_SAMPLER=always_on
networks:
- gotenberg_net
restart: unless-stopped
services:
gotenberg:
<<: *gotenberg-base
# no ports: instances are reachable only via the internal network
health-check:
test: ["CMD-SHELL", "curl --fail http://127.0.0.1:3000/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
gotenberg_net:
external: true
The configuration shown allows us to maintain a single configuration regardless of the number of gotenberg containers we will run. The health-check function will be used by Neteye to verify the operation of the different containers.
The OTEL-COLLECTOR is also containerized. The configuration extracts the crucial information from the .env file present in the root of the project. Here is an example:
.env file
# Common values shared by compose files
PROJECT_NAME=gotenberg
OTEL_COLLECTOR_HOST=otel-collector
OTEL_COLLECTOR_OTLP_HTTP_ENDPOINT=http://otel-collector:4318
GOTENBERG_IMAGE=gotenberg:custom
APM_ENDPOINT=https://myapmendpoint:8200
APM_TOKEN=mysupersecrettoken
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
container_name: otel-collector
ports:
- "4317:4317"
- "4318:4318"
volumes:
- ./otel/collector-config.yaml:/etc/otel/config.yaml:ro
environment:
APM_ENDPOINT: "${APM_ENDPOINT:?APM_ENDPOINT is required}"
APM_TOKEN: "${APM_TOKEN:?APM_TOKEN is required}"
command: ["--config", "/etc/otel/config.yaml"]
networks:
- gotenberg_net
restart: unless-stopped
networks:
gotenberg_net:
external: true
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
memory_limiter:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 64
exporters:
otlp_http/elastic:
endpoint: "${APM_ENDPOINT}"
headers:
Authorization: "Bearer ${APM_TOKEN}"
tls:
insecure_skip_verify: true
debug:
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http/elastic, debug]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http/elastic, debug]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp_http/elastic, debug]
Run the container , try a test print and verify the data sent by the collector on the Elastic APM component.



That’s it.
As you can see, this makes it very easy to monitor the inner workings of a third-party container.
Write to us whether you have any doubts or are just curious.
Did you find this article interesting? Does it match your skill set? Our customers often present us with problems that need customized solutions. In fact, we’re currently hiring for roles like this here at Würth IT Italy.