Creating A GraphicsMagick Playground With Docker, CommandBox, And Lucee CFML
When Adobe first introduced the CFImage
tag in ColdFusion 8, I thought it was the bee's knees! Suddenly, this language that made everything else easy now made image manipulation easy as well. In the years since then, however, I've grown to view ColdFusion's image manipulation with a bit of hesitation. In fact, I'm interested in moving more of my image manipulation over to GraphicsMagick, a command-line utility that's completely dedicated to image manipulation. In order to get a sense of what that journey might entail, I wanted to create a little GraphicsMagick playground for myself using Docker, CommandBox, and Lucee CFML.
To get this started, I created a Dockerfile
that extends one of the [CommandBox Docker images] provided Ortus Solutions. This gives me access to Lucee CFML and - via the CFConfig
utility - an easy-peasy way to configure the Lucee Server. At this point, all I'm doing is installing the graphicsmagick
package using apt-get
:
# Use the CommandBox base image.
FROM ortussolutions/commandbox:lucee5
# Some of the GraphicsMagick features require additional libraries to be installed. Those
# can be seen here: http://www.graphicsmagick.org/README.html . That said, it appears
# that a good number of packages get automatically installed using the following apt-get.
# --
# TODO: Should I be pinning graphicsmagick and all of its dependencies?
RUN apt-get update && apt-get install -y \
graphicsmagick \
&& apt-get clean
I wanted to get this GraphicsMagick playground to use the same version of Lucee CFML that I use at work (5.2.9.40); however, I couldn't figure out how to do that. According to the Docker detail page, I think I'm supposed to be able to use the ENV variable, CFENGINE
, to make this happen (ex, lucee@5.2.9.40
). But, I kept ending up with a different version of Lucee in the running container.
Honestly, I'm a noob at Docker - so, I am sure that this feature is documented; only, I don't know enough about Docker to understand all of what I'm reading.
I also wanted to pin or lock my GraphicsMagick packages to a specific version so that the building of this Docker image would be deterministic. However, I'm even more unfamiliar with apt
than I am with Docker
- oh noes! As such, I wasn't sure how to go about this in the right way.
I understand that I can lock the graphicsmagick
page like this:
graphicsmagick=1.3.28-2ubuntu0.1
However, when I ran the Docker build this way, I noticed that a whole bunch of other packages also get installed:
The following additional packages will be installed:
dbus fonts-droid-fallback fonts-noto-mono ghostscript gsfonts libapparmor1 libavahi-client3 libavahi-common-data libavahi-common3 libbsd0 libcups2 libcupsfilters1 libcupsimage2 libdbus-1-3 libgomp1 libgraphicsmagick-q16-3 libgs9 libgs9-common libicu60 libidn11 libijs-0.35 libjbig0 libjbig2dec0 libjpeg-turbo8 libjpeg8 liblcms2-2 libpaper-utils libpaper1 libtiff5 libwebp6 libwebpmux3 libwmf0.2-7 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 multiarch-support poppler-data
Suggested packages:
default-dbus-session-bus | dbus-session-bus fonts-noto ghostscript-x graphicsmagick-dbg cups-common liblcms2-utils libwmf0.2-7-gtk poppler-utils fonts-japanese-mincho | fonts-ipafont-mincho fonts-japanese-gothic | fonts-ipafont-gothic fonts-arphic-ukai fonts-arphic-uming fonts-nanum
Would I have to go pin each of those other apt
packages as well? Or, does pinning the graphicsmagick
package itself implicitly lock all those other packages that get automatically installed?
I hate how unsure I am about so much of the Docker stuff still.
Anyway, once I had my Dockerfile
, I then set up my .cfconfig.json
file to give my Lucee CFML server a password:
{
"adminPassword": "password"
}
The CFConfig
utility accepts dozens of additional configuration objects. But, for my purposes, I just want to make sure I can get into the Admin if I need to.
With my Dockerfile
and my .cfconfig.json
file created, I just needed to wire it all together with a docker-compose.yml
file that mounts the current directly as volume inside the server's app-root:
version: "2.4"
services:
lucee:
build: "."
ports:
# Server administrative URLs:
# --
# Server: http://localhost:8080/lucee/admin/server.cfm
# Web: http://localhost:8080/lucee/admin/web.cfm
- "8080:8080"
volumes:
- ".:/app"
environment:
APP_DIR: "/app/wwwroot"
# We are using the CommandBox image as the base for our container. As such, we can
# use the CFConfig utility to configure out ColdFusion settings (such as the Admin
# password).
cfconfigfile: "/app/.cfconfig.json"
Then, it was a mere docker-compose up --build
and I was off to the races! In just seconds (not including the docker pull
that takes place), I had a running Lucee CFML server on port :8080
. Time to try out some GraphicsMagick.
To make sure everything was working, I created a Lucee CFML page that takes a given image and generates several thumbnails at various sizes using the CFExecute
tag:
<cfscript>
startedAt = getTickCount();
sizes = [ 500, 400, 300, 200, 100, 50 ];
for ( size in sizes ) {
inputFilepath = expandPath( "../images/boop.jpg" );
outputFilename = "output-#getTickCount()#-#size#.jpg";
outputFilepath = expandPath( "./#outputFilename#" );
// All of the image utilities are provided _through_ the GraphicsMagick binary.
command = "gm";
// We're going to use the Convert utility for this test.
utility = "convert";
// We want to be EXPLICIT about which input reader GraphicsMagick should use.
// If we leave it up to "automatic detection", a malicious actor could fake
// file-type and potentially exploit a weakness in a given reader.
// --
// READ MORE: http://www.graphicsmagick.org/security.html
utilityReader = "jpg";
// Setup the options for the Convert utility.
commandOptions = [
utility,
// The -size options gives a hint to the JPEG decoder that the image is going
// to be downscaled, allowing it to run faster by avoiding returning full-
// resolution images to GraphicsMagick for the subsequent resize.
"-size #size#x#size#",
// Provide the source image to be read with the an explicit reader.
( utilityReader & ":" & inputFilepath ),
// We want to reduce the image so that it fits to a WIDTH of "Size" (using
// the ^ minimum indicator and omitting the height geometry). This may create
// a height that is larger than Size.
"-thumbnail #size#x^",
// Trim any Height off the thumbnail that exceeded Size.
"-crop #size#x#size#",
// Set the amount of compression to apply to the generated image.
"-quality 80",
// Reduce the contrast a bit (this test image is somewhat dark).
"+contrast",
// Remove all meta-data information from the file since this isn't needed for
// the thumbnail. This will make the image smaller for web-delivery.
"-strip",
// Provide the output image file - the file-extension will determine the
// format of the generated image.
outputFilepath
];
// Execute GraphicsMagick on the command-line.
execute
name = command
arguments = commandOptions.toList( " " )
variable = "successResult"
errorVariable = "errorResult"
timeout = 5
;
// If the error variable has been populated, it means the CFExecute tag ran into
// an error - let's dump-it-out and halt processing.
if ( variables.keyExists( "errorVariable" ) && errorVariable.len() ) {
dump( errorVariable );
abort;
}
// Output the resized image along with its file-size.
echo( "<p>" );
echo( "<img src='#outputFilename#' /> <br />" );
echo( "Size: #size#px" );
echo( " — " );
echo( "Bytes: #numberFormat( getFileInfo( outputFilepath ).size )#" );
echo( "</p>" );
} // END: For-loop.
echo( "Duration: #numberFormat( getTickCount() - startedAt )# ms" );
</cfscript>
This ColdFusion code iterates over the array of dimensions; and, for each dimension, thumbnails the original image and writes it to the response. And, when we run this CFML, we get the following browser output:
As you can see, I was able to use Lucee CFML to generate thumbnails of Lucy using a Docker container being managed by CommandBox!
I am sure that the Docker portions of this setup leave much to be desired - like I said, I'm a Docker noob. But, at the end of the day, I now have a Lucee CFML server that I can spin up in seconds in my local development environment. This should be sufficient for me to start playing around with recreating CFImage
features in GraphicsMagick.
Want to use code from this post? Check out the license.
Reader Comments
@All,
As I was working on this, I came across some good GraphicsMagick inspiration in the ColdFusion community:
James Moberg - https://gist.github.com/JamoCA/c386c7621d028941587d
Gray Stanton - https://github.com/GaryStanton/ColdThumbs
I haven't looked through all of it yet; but, definitely some good learning to be had.