Latest Elastic APM Agent in Docker
How to automatically download and use the latest version of the Elastic APM Agent in your Java application Docker image.
Conclusion First
# https://olof.tech/latest-elastic-apm-agent-in-docker/
FROM alpine:latest AS builder-elastic-apm-agent-downloader
RUN apk --no-cache add curl jq
RUN ELASTIC_APM_AGENT_VERSION=`curl --silent --fail --show-error "https://search.maven.org/solrsearch/select?q=g:co.elastic.apm+AND+a:elastic-apm-agent&start=0&rows=20" | jq --raw-output --exit-status ".response | .docs[0] | .latestVersion"` \
&& echo "Elastic APM Agent Version: ${ELASTIC_APM_AGENT_VERSION}" \
&& curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
FROM eclipse-temurin:11
RUN mkdir /opt/app
COPY app.jar /opt/app/app.jar
COPY --from=builder-elastic-apm-agent-downloader /elastic-apm-agent.jar /
ENTRYPOINT java -javaagent:/elastic-apm-agent.jar -jar /opt/app/app.jar
Hardcoding is Simple
Adding the Elastic APM Agent to a Java application Dockerfile
is this simple if you just hardcode the version:
FROM eclipse-temurin:11
RUN mkdir /opt/app
COPY app.jar /opt/app/app.jar
ARG ELASTIC_APM_AGENT_VERSION="1.34.0"
RUN curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
ENTRYPOINT java -javaagent:/elastic-apm-agent.jar -jar /opt/app/app.jar
The Latest Version?
What if you always want to download the very latest version?
According to the Elastic documentation there is no correlation between the version of the APM agent and the Elastic stack. That means you can update the elastic-apm-agent.jar
to latest version without updating your Elastic stack.
I suggest always using the latest version to benefit from instrumentation improvements and security patches. In fact the Elastic APM Agent at one point contained the Log4Shell
vulnerability 🐛. Staying up to date can some times be important for real 😱.
Sure, the benefit of just hardcoding is you know exactly which version you are running. But will you really remember to keep it updated? New versions of the elastic-apm-agent.jar
are frequently released. It was released 16 times during 2021 alone.
Quick and Dirty
Here's a quick and dirty solution you may like:
FROM eclipse-temurin:11
RUN mkdir /opt/app
COPY app.jar /opt/app/app.jar
RUN ELASTIC_APM_AGENT_VERSION=`curl --silent --fail --show-error "https://search.maven.org/solrsearch/select?q=g:co.elastic.apm+AND+a:elastic-apm-agent&start=0&rows=20" | grep -Po 'latestVersion.:.\K[^"]*'` \
&& curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
ENTRYPOINT java -javaagent:/elastic-apm-agent.jar -jar /opt/app/app.jar
A downside with this solution is that curl
must be installed on the base image.
The grep -Po 'latestVersion.:.\K[^"]*'
is a neat trick used to parse the version from the JSON returned. Credit goes to https://kthoms.wordpress.com/2019/03/06/get-the-latest-artifact-version-from-maven-central-via-shell-command/. It does not work with macOS favored grep
however, making it hard to debug. Perhaps using jq
would be better?
Docker Multi-Stage Build
I propose using a Docker multi-stage build 🐳🐳. This way we can ensure access to useful commands such as curl
and jq
without bloating the final image size. It also decouples the download logic really well. Once we get it right it will work for any base image.
We can leverage the jq
flag --exit-status
to make the Dockerfile
build fail if the jq
-query does not yield a result.
We can also add an echo
with the version number into the mix:
# https://olof.tech/latest-elastic-apm-agent-in-docker/
FROM alpine:latest AS builder-elastic-apm-agent-downloader
RUN apk --no-cache add curl jq
RUN ELASTIC_APM_AGENT_VERSION=`curl --silent --fail --show-error "https://search.maven.org/solrsearch/select?q=g:co.elastic.apm+AND+a:elastic-apm-agent&start=0&rows=20" | jq --raw-output --exit-status ".response | .docs[0] | .latestVersion"` \
&& echo "Elastic APM Agent Version: ${ELASTIC_APM_AGENT_VERSION}" \
&& curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
FROM eclipse-temurin:11
RUN mkdir /opt/app
COPY app.jar /opt/app/app.jar
COPY --from=builder-elastic-apm-agent-downloader /elastic-apm-agent.jar /
ENTRYPOINT java -javaagent:/elastic-apm-agent.jar -jar /opt/app/app.jar
Try it Yourself
If you have Docker installed on your system you can try this solution out yourself right now.
You don't even need a Dockerfile
. Just execute these commands:
# Start and enter a temporary Alpine container
docker run --rm -it --entrypoint /bin/sh alpine:latest
# Install curl and jq
apk --no-cache add curl jq
# Download the latest elastic-apm-agent.jar
ELASTIC_APM_AGENT_VERSION=`curl --silent --fail --show-error "https://search.maven.org/solrsearch/select?q=g:co.elastic.apm+AND+a:elastic-apm-agent&start=0&rows=20" | jq --raw-output --exit-status ".response | .docs[0] | .latestVersion"` \
&& echo "Elastic APM Agent Version: ${ELASTIC_APM_AGENT_VERSION}" \
&& curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
# Check that it's there
cd /
ls -ahl
# Exit the temporary Alpine container
exit
Or alternatively you can create this Dockerfile
:
# https://olof.tech/latest-elastic-apm-agent-in-docker/
FROM alpine:latest AS builder-elastic-apm-agent-downloader
RUN apk --no-cache add curl jq
RUN ELASTIC_APM_AGENT_VERSION=`curl --silent --fail --show-error "https://search.maven.org/solrsearch/select?q=g:co.elastic.apm+AND+a:elastic-apm-agent&start=0&rows=20" | jq --raw-output --exit-status ".response | .docs[0] | .latestVersion"` \
&& echo "Elastic APM Agent Version: ${ELASTIC_APM_AGENT_VERSION}" \
&& curl --silent --fail --show-error "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${ELASTIC_APM_AGENT_VERSION}/elastic-apm-agent-${ELASTIC_APM_AGENT_VERSION}.jar" -o /elastic-apm-agent.jar
FROM eclipse-temurin:11
COPY --from=builder-elastic-apm-agent-downloader /elastic-apm-agent.jar /
ENTRYPOINT echo "Hello World!"
Then test it by executing these commands:
# Build the Dockerfile with the tag "test"
docker build . -t test
# Start and enter a temporary container
docker run --rm -it --entrypoint /bin/sh test
# Check that it's there
cd /
ls -ahl
# Exit the temporary Alpine container
exit
Also Keep in Mind
Running apps in Docker as root
is bad practice. It's common to create another user such as appuser
instead. The /elastic-apm-agent.jar
must be owned by the same user as the app.jar
for attaching it as an agent to work. You may have to add --chown=appuser:appuser
like this:
COPY --from=builder-elastic-apm-agent-downloader --chown=appuser:appuser /elastic-apm-agent.jar /