This morning, we released the official Docker CommandBox image v3.6.0, which adds support for customizing your server using environment variables in your build, including support for the conventions used by the cfconfig CommandBox module. While the module is still in alpha, it is already proving to be a powerful way to provision and configure CFML servers at runtime.

Why use Docker?

If you're not up to speed with what Docker is and how it can benefit you, throughout the dev-ops lifecycle of your application, have a look at this summary article on the benefits or dig in to the official documentation. As we get further in to the weeds with configuration, we'll assume you're up to speed on the basics.

Running a CFML Docker Container using the CommandBox image

Once you're up to speed with running Docker on your local machine, starting up a CFML server using the CommandBox image is as simple as:

docker pull ortussolutions/commandbox
docker run -p 8080:8080 -v "/path/to/your/app:/app" ortussolutions/commandbox

This will serve a CFML application on port 8080 (which is the default $PORT variable of the image) of the active Docker engine. If you have a server.json file in the root of your directory, all of those config settings will be transferred over, with the exception of three, which will be overridden by the environment variables and used to expose the image: web.http.port, web.ssl.port (if applicable), and host. The variables $PORT and $SSL_PORT may be specified when running the container, and will be used in place of the defaults. This allows you to prevent conflicts with other containers (e.g. Tomcat) which may use the default port. Note the -v directive in the docker run command which mounts your application in to the /app directory of the container - which is the convention path CommandBox uses to serve your application.

The implementation problem of the above command is that it doesn't allow us to really deploy a configured CFML server. Through the 3.5.0 docker image, the only way to do that was to specify a custom app.serverHomeDirectory in your server.json file and then commit only your configuration files. Example .gitignore for an ACF2016 server confiugured with an app.serverHomeDirectory of /app/.engine (placed inside your .engine directory):

*
!.gitignore

!/WEB-INF/cfusion/lib/neo-*.xml
!/WEB-INF/cfusion/lib/*.properties
!*/

In the above .gitignore, we are only persisting the .properties and neo-*.xml files used to configure the Coldfusion 2016 server. When the CFML engine WAR is exploded on the startup of your engine, those config files will persist and your server is pre-configured. You can still do this, however CFConfig offers a way to accomplish the same thing without any configuration files in place through the use of environment variables. A quick tour of the available environment variables:

  • $PORT - The port which your server should start on. The default is 8080.
  • $SSL_PORT - If applicable, the ssl port used by your server The default is 8443.
  • $CFENGINE - Using the server.json syntax, allows you to specify the CFML engine for your container (i.e. - adobe@2016 or lucee@5 )
  • $cfconfig_[engine setting] - Any environment variable provided which includes the cfconfig_ prefix will be determined to be a cfconfig setting and the value after the prefix is presumed to be the setting name. The command cfconfig set ${Setting: settingName not found}=${Setting: value not found} will be run to populate your setting in to the $SERVER_HOME_DIRECTORY You can checkout an example config file here.
  • $CFCONFIG - If you need advanced configuration - datasources, caches, timeout settings, etc - a cfconfig-compatible JSON file may be provided with this environment variable. The file will be loaded and applied to your server. For Lucee servers, if an adminPassword key exists, it will be applied as the Server and Web context passwords.
  • $SERVER_HOME_DIRECTORY - When provided, a custom path to your server home directory will be assigned. By default, this path is set as /root/serverHome ( Note: You may also provide this variable in your app's customized server.json file )

If you already have a pre-configured CFML server you'd like to use, simply install cfconfig and save your existing setting with the following commmand:

box cfconfig export myConfig.json /path/to/my/existing/server/install adobe@2016

View the docs for more config, transfer, import and export information.

Once you have your cfconfig file set, a simple docker run command would be:

docker run -p 8080:8080 -e "CFCONFIG=/app/myConfig.json" -v "/path/to/your/app:/app" ortussolutions/commandbox

The -e "CFCONFIG=/app/myConfig.json" argument tells docker to set an environment variable of CFCONFIG to /app/myConfig.json. If that file exists when the Docker container is started, the configuration will be automatically applied. (Note: For additional security, you can mount your config in to a non-app path and simply change the environment variable above.)

Compose It

In reality, deploying a CFML server usually requires additional connection dependencies. If you need to deploy those dependencies along with your application, the easiest way to do so is using Docker Compose. It also provides a much more human-readable configuration, in comparison to script one-liners.

Let's say we want to deploy a self-contained stack that includes your CFML server, MySQL server, and reverse proxy your web traffic via NGINX. A single compose file in the root of your app, which connects the three of these containers might look like:

version: '2.1'

services:
  # NGINX Container
  web:
    image: nginx
    ports: 
      - "80:80"
      - "443:443"
    # Mount our shared webroot volume
    volumes:
      - .:/usr/share/nginx/html
      - ./build/docker/nginx/nginx.conf:/etc/nginx/nginx.conf
  
  #Application Server
  app:
    image: ortussolutions/commandbox 
    environment:
      PORT: 8080
      SSL_PORT: 8443
      CFCONFIG: /app/cfConfig.json
    volumes:
      - .:/app

  #MySQL
  mysql:
    image: mysql:latest  
    environment:
      MYSQL_ROOT_PASSWORD: "MyS3cur3P455!"
    volumes:
      - ./build/docker/mysql/initdb:/docker-entrypoint-initdb.d
      - /my/host/datadir:/var/lib/mysql
    ports:
      - "3306:3306"

There are a few things going on in this file, of note:

  1. We've mounted our NGINX configuration as well as the application in to the convention routes for configuration and the HTML root. You can skip the HTML root mounting, if you wish, as CommandBox will also serve your static files. We would need to provide the appropriate proxy settings from nginx to the host app:8080 in order for NGINX to deliver our CFML, however
  2. We've provided our CFCONFIG environment variable to our app container, which will be used to automatically configure the CFML server.
  3. We've mounted an initdb directory in to the MySQL container's /docker-entrypoint-initdb.d directory. The MySQL image will pick up any scripts in this directory, when it inits and run those scripts automatically
  4. We've mounted /my/host/datadir in to /var/lib/mysql in the MySQL container to allow our data files to persist between builds of the container.

Now, to start this "stack", from the root of our project, we can simply type:

docker-compose up --build

Your terminal will be filled with the results of the individual containers starting up, which may help you to debug configuration issues within the stack.

To bring up the stack quietly, use docker-compose up -d, which will daemonize it.

Docker is increasingly becoming a powerful tool for orchestrating, scheduling, and deploying applications. It can also provide a powerful framework for common development environments for teams. If you're new to Docker, feel free to take the image for a spin. Once you pass the initial learning curve, we think you'll be excited about this powerful new tool in your toolbox!