CFWheels, meet Docker

July 20, 2015 · Chris Peters

I was stoked when I saw that Adam Chapman had created a Docker setup for CFWheels and Lucee. If you're familiar with CFWheels, this is a great way to jump in and see if it's right for you.

I was stoked when I saw that Adam Chapman had created a Docker setup for CFWheels and Lucee.

Docker is a fairly easy way to get a development environment setup for experimentation, which then can be shipped into production later. It’s also a great way to try out Lucee and CFWheels.

You’re probably like I was also: curious about Docker and what it could do for you. If you’re familiar with CFWheels, this is a great way to jump in and see if it’s right for you.

Installing Docker and Docker Compose

To use such a setup, you need Docker and Docker Compose installed on your local machine. This depends on the operating system, but for our Macs, it involved using Homebrew to install boot2docker and fig (that’s how it’s named in Homebrew because they won’t change the name for some unknown reason).

I found Josh Clayton’s instructions to be helpful when I was first starting out with Docker.

Our own boilerplate on GitHub

Major props to Adam for sharing his implementation online. But I forked it and tweaked things a little.

The main difference is changing the Ubuntu image that the Dockerfile is based on to Phusion’s Baseimage-docker. I recommend reading up on Baseimage-docker after you get your feet wet.

I’ve also taken the liberty to put a blank CFWheels application in the app folder and configured it to take advantage of Docker Compose’s environment settings. (More on that in a minute.)

Installation

After you download the code, copy docker-compose.yml.sample into docker-compose.yml, and run this command to build the Docker image:

$ docker-compose build
view raw build.sh hosted with ❤ by GitHub

It takes a few minutes to download and install Lucee, but don’t worry: this Lucee installation will be cached for your application unless you change the version number in the Dockerfile. (Hence upgrading Lucee is as easy as changing the version number in your Dockerfile and running docker-compose build.)

To run the application, run the docker-compose up command and visit http://192.168.59.103:3000 in your browser.

(Note: Port 3000 can be changed to whatever you want in docker-compose.yml. Also note, if you can’t connect to your application, try running boot2docker ip to make sure you should in fact be hitting the standard 192.168.59.103 IP address.)

Configuration via environment variables

I also set up a sample docker-compose.yml file that does something a little different than most ColdFusion developers are used to:

web:
build: .
command: ./start.sh
ports:
- "3000:80"
volumes:
- ./app/:/var/www/
environment:
WHEELS_ENV: design
WHEELS_RELOAD_PW:
DB_TYPE:
DB_HOST:
DB_PORT:
DB_DATABASE:
DB_USERNAME:
DB_PASSWORD:
SMTP_SERVER:
SMTP_PORT:
SMTP_USESSL:
SMTP_USETLS:
SMTP_SIGN:
SMTP_USERNAME:
SMTP_PASSWORD:
SMTP_BCC:
SMTP_CHARSET:
SMTP_TIMEOUT:
SMTP_FAILTO:
SMTP_KEYSTORE:
SMTP_KEYSTOREPASSWORD:
SMTP_KEYALIAS:
SMTP_KEYPASSWORD:

Notice the environment hash. That sets up environment variables to be picked up by the operating system and passed on to Lucee. That’s where you would setup your data source according to Lucee’s documentation. (See config/app.cfm for a peek at where these settings are being used.)

That’s right; my aim is to not use the Lucee Administrator for anything so that I can configure environment variables in docker-compose.yml, add mappings and/or custom tag paths to config/app.cfm, deploy code, and be done with it.

Here’s what configuring a data source looks like with this setup:

<cfscript>
// Setup environment variables.
server.ENV = CreateObject("java", "java.lang.System").getenv();
// Setup data source.
this.DataSources["www"] = {
type=server.ENV.DB_TYPE,
host=server.ENV.DB_HOST,
port=server.ENV.DB_PORT,
database=server.ENV.DB_DATABASE,
username=server.ENV.DB_USERNAME,
password=server.ENV.DB_PASSWORD
};
</cfscript>
view raw app.cfm hosted with ❤ by GitHub

You could also add other keys for things that change depending on the environment you have configured, like 3rd party API credentials, SMTP servers, your Google Analytics Tracking Code, or whatever.

With that server.ENV variable setup in the above example app.cfm, you should be able to write logic within your application based on the values instead of the CFWheels environment you’re in:

<cfoutput>
<!--- Instead of a lame check like `<cfif get("environment") eq "production">`,
we can instead do whatever the environment is calling for --->
<cfif
StructKeyExists(server.ENV, "GOOGLE_ANALYTICS_TRACKING_CODE")
and Len(server.ENV.GOOGLE_ANALYTICS_TRACKING_CODE)
>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '#server.ENV.GOOGLE_ANALYTICS_TRACKING_CODE#', 'example.com');
ga('send', 'pageview');
</script>
</cfif>
</cfoutput>

A note about databases

Many examples of Docker Compose suggest that you also configure a Docker container for your database. I have yet to ever get that to work properly on my machine. Sure, it works, but then when I need to rebuild my environment with docker-compose build, it wipes out my database’s data.

I’ve tried every suggestion of mounting volumes or setting up volume containers via Docker Compose, but I’ve never been able to get it working properly.

That aside, in development, I suggest that you install the database directly on your development machine and setup the DB_HOST environment variable to reference the LAN IP address of your development machine.

On my Mac, I can get my LAN IP by running this command:

$ ipconfig getifaddr en1
view raw lan-ip.sh hosted with ❤ by GitHub

On Windows, you can run ipconfig and grab one of those IP addresses. (Can’t remember which one, and I have the lazy.)

Docker in production

Hosting Docker-based apps in production still seems to be in the “early adopter” phase. It’s probably possible to build your own Docker-running infrastructure. But for me, no thank you!

The easiest solution I’ve found so far has been Cloud 66, a PaaS provider that I’d halfway dare to call the “Heroku of Docker.” You point Cloud 66 at your Git repo, and on deploy, it pulls in your code and builds a Docker image on the fly based on your Dockerfile. (Or you can tell it to use your own pre-built image if you want a faster deployment.)

After that, you have your CFWheels app running in production with the exact same OS, Lucee runtime, and code that you were running in development. Win!

I’ve found configuring Docker services on Cloud 66 to be very similar to the docker-compose.yml file that I shared earlier in this post. They’ve done a nice job trying to keep things as consistent as possible. They’ve even chosen to host your database outside of Docker containers too, perhaps for the reasons I listed earlier.

Lastly, there is an interface for configuring your environment variables, or you can also configure those along with the services, just like with Docker Compose. There, you can set WHEELS_ENV to production and not have to worry about accidentally deploying a config/environment.cfm file that is set to design or development. (I’ll admit to having done that once or twice.)

Happy Dockering!

I’ve always been a fan of open source engines like Railo and Lucee because they make this sort of automation easier (and dare I say, possible) when compared to what Adobe ColdFusion licensing allows.

I’m sure I’ve left something out, so leave a comment or open an issue if you have questions or suggestions on how to improve this setup.

About Chris Peters

With over 20 years of experience, I help plan, execute, and optimize digital experiences.

Leave a comment