In the last few years, I have had some requirements to turn what are essentially command line programs into small microservices. Lucee is GREAT at making services, so to make these microservices I (lazily) decided to take the most direct path, and use Lucee Express.
Lucee Express is essentially Tomcat and Lucee all ready to run on Windows, macOS and Linux. Download unzip and run basically. This is perfect for this so the first thing I do is make a Docker image for this:
FROM alpine
ARG LUCEE_VERSION="5.3.10.28-SNAPSHOT"
ADD https://cdn.lucee.org/lucee-express-${LUCEE_VERSION}.zip lucee.zip
RUN mkdir /lucee && \
unzip lucee.zip -d /lucee && \
chmod +x /lucee/*.sh && \
chmod +x /lucee/bin/*.sh && \
rm -f lucee.zip && \
rm -rf /lucee/__MACOSX
COPY webroot /lucee/webapps/ROOT
RUN apk add openjdk11
ENTRYPOINT [ "/lucee/startup.sh" ]
To build the docker image above I do: docker build -t markdrew/lucee-light .
Let me run through it quickly:
FROM alpine
: Uses the alpine base image. This is a super tiny Linux distroARG LUCEE_VERSION="5.3.10.28-SNAPSHOT"
: this allows me to add the version when I build the image. I can just change the version of lucee by passing--build-arg LUCEE_VERSION=5.4
for exampleADD https://cdn.lucee.org/lucee-express-${LUCEE_VERSION}.zip lucee.zip
downloads the lucee version to lucee.zip- The next
RUN
line is more complex, but basically unzips lucee.zip and makes the startup.sh
files executable and then deletes the lucee.zip and extra__MACOSX
folder that was uncompressed from the zip. COPY webroot /lucee/webapps/ROOT
we copy our code to the lucee web root. This is our app so we have to run it.RUN apk add openjdk11
We install the JDK! Cant run lucee without a JDKENTRYPOINT [ "/lucee/startup.sh" ]
finally we set the entry point, the script we run when we start.
And that is it. So after building, we get the following when we run docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
markdrew/lucee-light latest b7085aaed9a9 9 minutes ago 648MB
Ok, 648MB… it’s ok but not good enough really. A colleague (Zac Spitzer) mentioned that we don’t actually need the full JDK, so we went to get just the JRE. This changed the line: RUN apk add openjdk11
to RUN apk add openjdk11-jre
This gave a nice cut down:
REPOSITORY TAG IMAGE ID CREATED SIZE
markdrew/lucee-light latest 71e1b58415f3 2 hours ago 355MB
355MB! That’s a great improvement!
Zac also reminded me that since I am basically not using any extensions such as database drivers or whatever, I can download a “light” version of Lucee, so I can now add the jar download for lucee-light after I download the express version:
...
ADD https://cdn.lucee.org/lucee-express-${LUCEE_VERSION}.zip lucee.zip
ADD https://cdn.lucee.org/lucee-light-${LUCEE_VERSION}.jar lucee-light.jar
...
And in the RUN command where we expand Lucee, we can swap out the “fat” lucee.jar with the “light” one.
RUN mkdir /lucee && \
...
rm -f /lucee/lib/ext/lucee.jar && \
mv lucee-light.jar /lucee/lib/ext/lucee.jar && \
...
Ok! This should remove a big chunk of the filesize!
After building I get…
REPOSITORY TAG IMAGE ID CREATED SIZE
markdrew/lucee-light latest ae4bfae156db 1 second ago 320MB
Ok, not the greatest improvement. What is going on? I was expecting a lot less in there. Let’s investigate! There is a great tool to inspect docker images called dive that is perfect for this. It will look into our Docker image and tell us what is going on.
Let’s inspect our image:
dive markdrew/lucee-light
If we look at the summary it says:
Image name: markdrew/lucee-light
Total Image size: 320 MB
Potential wasted space: 111 MB
Image efficiency score: 65 %
Where is that 111MB of wasted space coming from? Well, despite deleting the zip files we don’t need they are still defined in each layer. They were added at the top of the previous one and are still loitering in our image. One way to remove these is to create a multi-stage build script.
Here is the script with changes:
FROM alpine as base
ARG LUCEE_VERSION="5.3.10.28-SNAPSHOT"
ADD https://cdn.lucee.org/lucee-express-${LUCEE_VERSION}.zip lucee.zip
ADD https://cdn.lucee.org/lucee-light-${LUCEE_VERSION}.jar lucee-light.jar
RUN mkdir /lucee && \
unzip lucee.zip -d /lucee && \
chmod +x /lucee/*.sh && \
chmod +x /lucee/bin/*.sh && \
rm -f lucee.zip && \
rm -f /lucee/lib/ext/lucee.jar && \
mv lucee-light.jar /lucee/lib/ext/lucee.jar && \
rm -rf /lucee/__MACOSX
COPY webroot /lucee/webapps/ROOT
FROM alpine
RUN apk add openjdk11-jre
COPY --from=base /lucee /lucee
ENTRYPOINT [ "/lucee/startup.sh" ]
The main changes are:
FROM alpine as base
we create a base image where we do the Lucee installationFROM alpine
again. This is our next stack (or final image)COPY --from=base /lucee /lucee
we now copy from our base image the Lucee folder we prepared. This will be our final image which ignores all the previous steps, which were there just to build the/lucee
folder. Those steps don’t need to live on in our final image.
After building we get:
REPOSITORY TAG IMAGE ID CREATED SIZE
markdrew/lucee-light latest deac63b18892 1 second ago 210MB
210MB vs. 648MB. I will buy that for a dollar!
Ok, let’s start it up! docker run --rm --name lucee-light markdrew/lucee-light
This takes: 1604 ms
Not bad, but we could pre-warm our image when we build it. This would expand all the required files that lucee needs and let lucee do whatever config it needs to do at startup. We don’t need to do this every time we start a container, so let’s add it to our build!
We need to add the warmup before our ENTRYPOINT
COPY --from=base /lucee /lucee
RUN LUCEE_ENABLE_WARMUP=true /lucee/startup.sh
ENTRYPOINT [ "/lucee/startup.sh" ]
The command RUN LUCEE_ENABLE_WARMUP=true /lucee/startup.sh
sets an environment variable called LUCEE_ENABLE_WARMUP
and then runs lucee. This means Lucee will start, warmup and then shut down.
Let’s try this build :
REPOSITORY TAG IMAGE ID CREATED SIZE
markdrew/lucee-light latest 3d97c6111b99 Less than a second ago 239MB
Ok, the size has increased a little, which will be the addition of new files created by lucee. When we start up this image it now takes 1194 ms. Nice! 410 ms off our speed, nearly cut down by a third in this case!
I realise there might be even more speed improvements for our light image (I would love to hear them!) but hopefully this post has shown you some ideas for your own images and adventures with Lucee!
UPDATE: you can now check out the code from here: https://github.com/cybersonic/super-slim-lucee-docker/tree/main
Tweet
comments powered by Disqus