Building Custom Lucee Docker Images

Lucee has had support for Docker with the latest and greatest images on Docker Hub for several years now. They are generated as part of the build process by Travis CI and come in a few different variations like different versions of Java, Tomcat, and Lucee.

In this post I will cover a different project which makes it easy to build custom Lucee Docker images. The project has three main features, which allow you to specify the following at build-time:

  1. The Lucee version
  2. Extensions to be added
  3. The Lucee Admin password

In addition to those settings, you can also easily map a local directory to the webroot. That allows you to map your application code at run-time and test it on the different Lucee and/or extensions versions.


The only prerequisite is to have Docker installed. That is required in order to enable the commands docker and docker-compose in your environment.

Getting Started

Grab a copy of the project from If you use git, you can go ahead and run git clone and clone it to a local working directory. Otherwise, simply click the green “Clone or download” button on the GitHub page, choose “Download ZIP”, and extract the contents of “lucee-docker-master” to a local working directory.

Open a console window, (Terminal, Command Prompt, etc.), and switch to the working directory. Now you are ready to execute the docker commands.

Building a Docker Image

We build a Docker image using the command docker image build .. That . at the end specifies the directory, and since we call the command from the current directory we pass a dot.

It is often useful to tag the image so you can reuse it easily, and possibly even push (upload) it to your Docker repository, or your account at Docker Hub. You tag an image with the argument -t <username>/<tagname>. To build an image and tag it as lucee5 under my username (isapir), I would use the command:

docker image build . -t isapir/lucee5

Docker will then download the base image of OpenJDK and Tomcat, and install on top of it the version of Lucee as specified in the Dockerfile. It will launch Lucee and run until the onServerStart() event is processed, and then shut it down and save the image. That “warmup” is done at build-time so that when you run the container you don’t have to wait for all of the initial setup.

Building a Custom Version

Building an image of a custom Lucee version is easy. The only piece of information that you need is a full valid Lucee version number, which you can get from the Lucee Download [3] page. For example, at the time of this writing, the snapshot version is, so to build it, and tag it as “lucee-53720”, we will set the build-arg “LUCEE_VERSION=” as shown below.

Please note that the command was broken into multiple lines here for better readability, but that is not required. Also, I use here the \ at the end of each line in order to keep the multiple lines as a single command (on Windows you would use the ^ symbol), so be sure to remove it if you type it all on one line:

docker image build . \
    -t isapir/lucee-53720 \
    --build-arg LUCEE_VERSION=

The same build process happens as before, but now the specified version is downloaded and built into the image.

Let’s spin up the image and test it out by running the docker container run <options> <image-tag> command. I will map the TCP port 8080 on my machine to port 8080 of the Docker container, and give the container the name “lucee-8080” rather than get a random name or have to refer to it by its hash. I will also use the option --rm to remove the container once it’s stopped:

docker container run --rm --name=lucee-8080 -p 8080:8080 isapir/53270

That will launch the container and you should see the output in the console. Pointing a web browser to http://localhost:8080/ will show the project’s welcome page:

Let’s stop that container by hitting CTRL+C in the console window. Since we used the --rm switch the container is removed and we can reuse its name if we want.

Setting the Lucee Admin Password

To set the Lucee Admin Password at build-time simply add a build-arg, e.g. to set the password to “changeit”, add the following arg:

--build-arg LUCEE_ADMIN_PASSWORD=changeit

Now when you visit the Lucee Admin you will be able to log in with that password - don’t forget to change it :wink: - rather than be asked to set a password by saving a file to the Lucee Server directory.

Adding Lucee Extensions at Build Time

Adding extensions is equally easy. The required information here is the extension’s ID (which is a UUID), which can also be obtained from the Lucee Download page or the Lucee Admin. For example, the ID for the “MongoDB Datasource and Cache” extension is “E6634E1A-4CC5-4839-A83C67549ECA8D5B”. You can add it at build-time using the build-arg below (the name and version are optional):

--build-arg LUCEE_EXTENSIONS="E6634E1A-4CC5-4839-A83C67549ECA8D5B;name=MongoDB;version="

To add multiple extensions, separate them with a , and ensure that the extension ID is the first element in that comma-separated list.

Now if you build the image and then run it, you can log into the Admin and see that the MongoDB extension, which is not installed by default, is indeed installed and ready to use as soon as the container starts.

Adding Your Application Code

There are two approaches to adding your application code:

  1. Copy it into the image. The code then becomes part of the image and can not be easily modified. This approach is usually taken when building an image for production deployment. If you choose this approach then copy your code into <working-directory>/res/catalina-base/webapps/ROOT.

  2. Map a directory from your local drive onto the Docker container when you start the container. This approach is great for development as you can make changes on your local directory using your favorite text editor and it is updated for the container automagically.

We will focus here on the second approach.

Mapping a Local Directory to the Container

Mapping a local directory is done by adding the -v <local-directory>:<container-directory> switch to the docker container run command.

The <container-directory> for the web root is at /srv/www/webapps/ROOT, so if for example, if the local directory of your application code is on your hard drive at /webapp then you would add the switch -v /webapp:/srv/www/webapps/ROOT so the complete command would be something like:

docker container run \
    --rm --name=lucee-8080 -p 8080:8080 \ 
    -v /webapp:/srv/www/webapps/ROOT \

And of course you can use all of the other options that docker container run command offers, like running in detached mode using the -d switch, setting environment variables using the -e switch, etc.

Tip: if you want change the settings for Tomcat then simply map the local directory to the path /srv/www/ on the remote directory and follow the Catalina Base conventions. If you do that, be sure to have your app code in the subdirectory webapps/ROOT.


this is great, thanks @isapir.

Just be aware that if you use any scheduled tasks, there is a outstanding regression from 5.3.5 which causes a 1m delay on shutdown

1 Like

Quick update: I published a video tutorial that shows how to use this:

Feedback welcomed!


@isapir, thank you so much for taking your time for creating this video for the community. Still hadn’t the time to build an image, but watched the video very closely and in my opinion it is very straight forward. Really appreciate it.


Thanks @andreas, I appreciate the feedback! Sometimes at the absence of feedback one wonders whether this helps anyone so it’s great to hear :+1:

And please let me know if you have any feedback when you get a chance to build an image using that project.




@isapir Huge thanks for sharing this, i’m new with CF and lucee, and this kind of contents are really helpfull specially for new ones like me.
Is it possible to create a docker-compose with mysql and lucee without having to create the database manually from lucy server? something like an environment variable like --build-args

Best, Mauricio.

1 Like

You can set up a data source through ENV variables like so:


Yes, it’s possible, but I’m not sure what you’re trying to do exactly.

@modius shows one option which will allow you to set up a datasource to any reachable MySQL server, but in docker-compose you would normally have the database created as another service and refer to it from Lucee by name. The database service can mount a volume from your host machine for persistence.


Thanks @modius i will give it a try.
I was looking at your workbench setup, it was a great help to set the configuration volumes.

@isapir I wast just playing around, the issue I was having is that docker did not always assign the same ip to containers, so I had to reassign it by hand from the databases configuration on my Aplication.cfc. I managed to solve it by assigning a network with a subnet and set different ips to each container.

Keep up the good work :muscle:t2:
Best, Mauricio

Mauricio, you can use the service name instead of the IP address. Docker will resolve it properly.

Funny, I just did that same thing for a Demo I’m preparing for my presentation at ApacheCon:

        image: isapir/lucee-53866
            - ./www:/srv/www/webapps/ROOT
            - "8080:8080"
            - 8080

p.s. ApacheCon is Remote this year and if anyone is interested Free Registration is still open for the next several hours I believe. See link to my talk and the registration at

I’m hoping that’s the more modern GO set up that @justincarter devised and not the old vagrant implementation we evolved from :wink:

This still remains the best team set up for web app development on straight Docker environments across different operating systems; we use Windows, MacOs and Linux in house. I thoroughly recommend it as an approach.

I know this is a little old, but…
After having issues with getting lucee running with IIS - I thought I would try and give the docker image a go…

I’m using;
[GitHub - isapir/lucee-docker: Build Custom Lucee Docker Images]
Thanks @isapir

But I have a question, please.
is it just a matter of creating multiple hosts in the tomcat config for multiple applications - then recommitting the image to save changes?


Ok I so I have the following to start my docker image;

docker container run --add-host=windows: -p 8080:8080 -p 443:443 -p 80:80 \
-v /mnt/c/inetpub/wwwroot/gsn:/srv/www/app/webroot/gsn \
-v /mnt/c/inetpub/wwwroot/cfmapping:/srv/www/app/webroot/cfmapping \
-v /mnt/c/inetpub/wwwroot/dbsetup:/srv/www/app/webroot/dbsetup \
-v /mnt/c/inetpub/wwwroot/trigger:/srv/www/app/webroot/trigger \
-v /mnt/c/inetpub/wwwroot/vxmlsurvey:/srv/www/app/webroot/vxmlsurvey \
--name lucee-8080 gavinbaumanis/lucee-539141:Latest

(The add-host allows me to use the HOST PCs database.)

I have edited tomcat’s server.xml and added an extra Host;

<Host name="centra.localhost" appBase="webapps/gsn" unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve"
        pattern="%h %l %u %t &quot;%r&quot; %s %b"

I have added centra.localhost to the docker /etc/hosts file

but I am still having issues.
localhost:8080 - gives me the default cfml app from the image - which is fine.
localhost:8080/lucee/admin/server.cfm - works as expected

but centra.localhost:8080/index.cfm
also returns the default app

As always - Thanks.

You need to add the context directive to Tomcat. Don’t know which is the recommended way with docker. In Tomcat you can:

  1. Recommended by Apache Docs:
    Create the ROOT.xml in:

With the following content:

<Context docBase=“c:\wwwroot_yoursiteA”">
  1. This is not recommend by the Tomcat team anymore, but still works: Edit your server.xml and add the context directive there:

<Context path="" docBase="c:\wwwroot_yoursiteA" />

<Host name="centra.localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
    <Context path="" docBase="c:\wwwroot_yoursiteA" />
    <Valve className="org.apache.catalina.valves.AccessLogValve"
        pattern="%h %l %u %t &quot;%r&quot; %s %b"

Be carefull(!!!) to NOT set the docbase with same value as the appBase. These are very different things. Having the values set equal may result in multiple context creations.

Thanks for your help @andreas - I think I am close - but it is throwing an error;

I can’t work out how to get the first option to work
Can you confirm the path?


where is lucee\conf?

If I try with the 2nd method, Adding in an extra ;

<Host name="centra.localhost" appBase="webapps" unpackWARs="false" autoDeploy="false">
	<Context path="" docBase="/srv/www/app/gsn/" />
    <Valve className="org.apache.catalina.valves.AccessLogValve"
        pattern="%h %l %u %t &quot;%r&quot; %s %b"

I get the following error;

javax.servlet.ServletException: the web context [309b805cd42abd7aef98023d7e122a83] has defined the same configuration directory [/srv/www/catalina-base/lucee-web] as the web context [4e9bf5f1d43c9c61c370ce45dd5f01b2]

This path in the Context node;

I get by mounting a volume when I run the container;

docker container run --add-host=windows: \
-p 8080:8080 -p 443:443 -p 80:80 \
-v /mnt/c/inetpub/wwwroot/gsn:/srv/www/app/gsn \

As always - thanks for the help!

@Gavin_Baumanis I’m not envolved that much into docker to tell for sure. Maybe someone else may give a better advice than my shots into the dark. Docker is something on my to-do list.

Under a default installation you would set the docBase for the host centra.localhost in

You may need to create the directories and files if these aren’t present already.