William Lieurance's Tech Blog

Download a file in a openjdk Dockerfile without wget or curl


Java has enough smarts to pull a file from the internet and put it on disk. Let's use those smarts

I'm in the process today of docker-wrapping a project called photon which does normalized address searches of OpenStreetMap data. It's a neat app, written in java, embeds elasticsearch, does some cool stuff.

Importantly though, I'm real tired of having to do the whole complicated dance to install curl and download the file, then clean up after the download since curl isn't necessary in steady-state. I have to be root to get it to work, I'm never sure I cleaned up everything correctly, and it just feels icky.

apt-get update && apt-get install curl && curl "https://github.com/komoot/photon/releases/download/0.3.5/photon-0.3.5.jar" -o /photon/photon.jar && apt-get erase curl && rm -rf /var/lib/apt/lists/*

I'm already in a container (openjdk:11-slim) that has java in it, and java is a pretty complete language, I wondered if there was something new in more recent versions of java that would let me download a file with a oneliner there. Turns out there is.

Used to be if we wanted Java to do something we'd have to write big .java files, compile down to .class files, and then put enough glue in there to make it work. Newer versions let you run java against a .java file directly, but there's something even better we can do. Jshell is a REPL that takes away a lot of the annoyance of writing out short bits of java code that you want to run. It can even take a single line on stdin and execute it, which makes it super useful for this kind of purpose. But what line to use?

For the content, Java's NIO package lets you pipe a file directly to the filesystem without having to buffer the whole contents into memory. That sounds like what we're after. Some googling led me to the Channels interface, and then chaining some objects together like so

RUN echo 'new FileOutputStream("/photon/photon.jar").getChannel().transferFrom(java.nio.channels.Channels.newChannel(new URL("https://github.com/komoot/photon/releases/download/0.3.5/photon-0.3.5.jar").openStream()), 0, Long.MAX_VALUE);' | jshell -

Is it long and hideous? Absolutely. Does it work? Well enough. It avoids a swap over to the root user to do the apt stuff but still feels icky. Some small progress anyhow.