Docker Compose Environments for Coldfusion Development

A basic Docker-Compose setup for continuous integration with Testbox, Lucee, Commandbox, and MariaDB.

Hey folks! We'll be going over:

  1. Running Lucee and Commandbox with Docker
  2. Setting up MariaDB initialization scripts
  3. Running MariaDB with Docker
  4. Scripting our environment with Docker Compose
  5. Creating a datasource with environment variables

Lucee in the Docker with Commandbox

I'm using ortussolutions/commandbox:alpine for CF development on Docker. This makes it super (SUPER!) easy to get started with Coldfusion on a new machine, or if you've never played with it before. One Docker command is all that separates you from Coldfusion and Lucee.

Note: I'm using the alpine tag because it's almost half the size (500MB) of other containers. This means it's faster to download, upload, execute, etc. YMMV.

For a basic, I-Want-It-Now CF container, run this docker command to start a Lucee server on port 8010 for the current directory.

docker run \
-v "$PWD:/app" \
-p "8010:8080"
ortussolutions/commandbox:alpine

To install Testbox and get set up for testing, you'll need to

  1. add -e BOX_INSTALL=true to that docker command
  2. drop testbox as a dependency in your box.json
  3. copy the Testbox harness into your tests/ folder

This is basically what I did for my app. Please see other docs for Configuring Testbox or messing with the Commandbox Docker Image.

Initializing a MariaDB Database

The exact app I was testing required a database. I chose to use the MariaDB Docker image to keep my dev machine clean and installation easy.

First, we'll want to have some testable data in our database for Testbox to act against. In other words, we need an init script which will load data into the MariaDB database when the container loads. See MariaDB Docker page (italics mine):

When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mariadb services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.

So, basically this means that if we want any data to exist in our database on startup, then we should create a local (host) directory, dump in our SQL scripts, and setup a docker volume which points to the mariadb initialization folder. If you were using docker run that'd be something like -v tests/testdb:/docker-entrypoint-initdb.d.

Steps for Initializing MariaDB

  1. Create a testdb folder - mkdir tests/testdb
  2. Add your SQL to tests/testdb/startMyDB.sql
  3. Don't forget to CREATE TABLE!

Spin Up and Connect to MariaDB

Finally, we'll need to connect to MariaDB from outside the docker image, so we need to

  1. publish the MariaDB port
  2. Know what port to connect to in Coldfusion
  3. Know what hostname to connect to. By default, it's the container name, like mariadb.

If you want to lock this down a little, feel free to use a different port for the container to listen to, like 3333.

A simple docker run for MariaDB should look something like this:

docker run \
-v ./tests/testdb:/docker-entrypoint-initdb.d \
-p "3306:3306" \
-e MYSQL_USER=cfOnDocker \
-e MYSQL_PASSWORD=cf_Is_S0_Gr8 \
-e MYSQL_RANDOM_ROOT_PASSWORD=true \
-e MYSQL_DATABASE=myTestDB \
mariadb:latest

Notice those environment vars? Feel free to use a .env file, ENV shell variables, or other means to automate them. Personally, I used a .env file in the root, which then allows you to reference those variables within docker-compose.yml.

Scripting Our Environment with Docker Compose

At this point, I highly recommend you create a .env file in the project root for storing secrets - and make sure you add it to your .gitignore!

# do NOT commit me to git!

# environment vars for the mariadb instance
# these will also be referenced by Coldfusion when connecting to the database
MYSQL_USER=cfOnDocker
MYSQL_PASSWORD=cf_Is_S0_Gr8
MYSQL_RANDOM_ROOT_PASSWORD=true
MYSQL_DATABASE=myDB

# environment vars for Coldfusion
CF_DB_HOST=mariadb
CF_DB_PORT=3306

Then docker-compose.yml, we'll reference those vars like CF_DB_PORT=${CF_DB_PORT}. Or, alternatively, we could use env_file: ./.env to pull in all the .env variables to each container without needing to specify them one by one in the docker compose file.

version: '3'
services:
  web:
    image: ortussolutions/commandbox:alpine
    ports:
      - "8010:80"
    volumes:
      - ./:/app
    environment:
      - APP_DIR=/app
      - BOX_INSTALL=true
      - PORT=80
      - HEADLESS=true
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_RANDOM_ROOT_PASSWORD=${MYSQL_RANDOM_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - CF_DB_HOST=${CF_DB_HOST}
      - CF_DB_PORT=${CF_DB_PORT}

  mariadb:
    image: mariadb:latest
    ports:
      - "3306:3306"
    volumes:
      - ./tests/testdb:/docker-entrypoint-initdb.d
    environment: # note: you could also use env_file: ./.env
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_RANDOM_ROOT_PASSWORD=${MYSQL_RANDOM_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}

Boom! This should be all we need to start up Lucee and MariaDB containers.

  • Run docker-compose up -d to start the test environment.
  • Run docker-compose logs to see all container logs.
  • Browse to http://localhost:8010/tests/runner.cfm to view and run tests.
  • Run docker-compose down to stop the test containers.

Using Environment Variables for a Test Datasource

Last thing - we need a datasource so we can connect to MariaDB. I used Lucee's server.system.environment struct in Coldfusion to pull in the environment variables passed to the container via docker compose.

Here's my datasource struct - I put this in /tests/Application.cfc so it was available for use while running my tests.

this.datasources["test"] = {
    class: 'com.mysql.jdbc.Driver'
    , bundleName: 'com.mysql.jdbc'
    , bundleVersion: '5.1.40'
    , connectionString: 'jdbc:mysql://#server.system.environment["CF_DB_HOST"]#:#server.system.environment["CF_DB_PORT"]#/#server.system.environment["MYSQL_DATABASE"]#?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=true'
    , username: "#server.system.environment["MYSQL_USER"]#"
    , password: "#server.system.environment["MYSQL_PASSWORD"]#"
    
    // optional settings
    , connectionLimit:5 // default:-1
};

February 6, 2019

« Four Things That Make Great Developers Great - Continuous (Coldfusion) Integration with Testbox and Bitbucket Pipelines »