With the rise of PaaS options, cloud-based and self-hosted, it's never been easier to rapidly deploy and scale apps. CommandBox now gives you a way to deploy CFML applications, using your choice of CFML engines, to the PaaS platforms Heroku and Dokku with our new Heroku Buildpack for Commandbox.
If you're not familiar with either of these PaaS implementations, click through the links above and then return here to see how to deploy your first CFML application on Heroku, using CommandBox to manage your CFML engine. A future post will discuss how to setup your own PaaS server using Dokku.
To begin, you'll need to install the Heroku command-line tools (as well as CommandBox). Mac and Windows installation instructions may be found here. Before executing the heroku commands below, you'll need to create an account and login with your Heroku CLI.
Once that's completed, lets create our first application:
mkdir dyno-might
cd dyno-might
Let's install a bare-bones Coldbox application, without database connectivity, for our example. We'll discuss the logistics of databases and shared files at the end of this post. (Note: Omit the box
from the following commands if you're already in the interactive shell)
box install coldbox
box coldbox create app dyno-might
Because we're going to deploy on Heroku's hobby tier, we need to bump the heap size for our Commandbox server to fall within the 350 megabytes of memory allocated to that dyno:
box server set jvm.heapsize=300
We'll also turn on rewrites at this time for friendly URLs:
box server set web.rewrites.enable=true
Now let's start our server to ensure the app is up and running:
box server start
We should see the default Coldbox app page displayed when we open our browser:
Now that we have the files we want to deploy in place. Let's create Git repository and add those files:
git init
git add --all
git commit -m 'Initial Release'
Let's move to creating our app on Heroku. With Heroku's CLI installed simply run heroku create [APP NAME HERE]
, which returns the following:
$ heroku create dyno-might
Creating ⬢ dyno-might... done
https://dyno-might.herokuapp.com/ | https://git.heroku.com/dyno-might.git
Note that two URLs are returned. The first is the public URL of your app. You can configure custom domain resolution within Heroku's tools. More information on custom domains is available here. The second URL returned is a special git "repository" for the app. The repository is special because pushes to the master branch will automatically trigger a build and deployment of your application, which makes deploying updates (and rolling back) a breeze.
Grab the second URL and configure a deployment remote for your git project:
git remote add heroku https://git.heroku.com/dyno-might.git
Next we need to set the buildpack for our application to our CommandBox buildpack, so that Heroku knows how to build the application:
heroku buildpacks:add https://github.com/Ortus-Solutions/heroku-buildpack-commandbox.git
Which returns
$ heroku buildpacks:add https://github.com/Ortus-Solutions/heroku-buildpack-commandbox.git --app dyno-might
Buildpack added. Next release on dyno-might will use https://github.com/Ortus-Solutions/heroku-buildpack-commandbox.git.
Run git push heroku master to create a new release using this buildpack.
Now that we've configured our buildpack, we're ready to deploy our application to Heroku. Your branch will automatically be on master
so simply run git push heroku master
and you'll see that, unlike a push to a regular Git repo, the output of the push shows your deployment status from start to finish:
$ git push heroku master
Counting objects: 319, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (294/294), done.
Writing objects: 100% (319/319), 892.75 KiB | 0 bytes/s, done.
Total 319 (delta 46), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> CFML app detected
remote: -----> Installing OpenJDK 1.8... done
remote: -----> Installing CommandBox
remote: % Total % Received % Xferd Average Speed Time Time Time Current
remote: Dload Upload Total Spent Left Speed
remote: 100 208 0 208 0 0 1078 0 --:--: -- --:--: -- --:--: -- 1083
remote: 100 38.5M 100 38.5M 0 0 21.1M 0 0:00: 01 0:00: 01 --:--: -- 27.5M
remote: Archive: /tmp/box.zip
remote: inflating: /tmp/build_48db30b8a18df3e0d0b039576c9fec7f/bin/box
remote: -----> Configuring CommandBox home: /app/.CommandBox (change with -CommandBox_home=/path/to/dir)
remote: Library path: /app/.CommandBox/lib
remote: Initializing libraries -- this will only happen once, and takes a few seconds...
remote: .........
remote: Libraries initialized
remote: CommandBox 3.3.0+00482 successfully installed
remote: -----> Configuring Container Host
remote: Set web.host = 0.0.0.0
remote:
remote: Set openbrowser = false
remote:
remote: -----> Host Configuration Complete
remote: -----> Commandbox Compilation Complete
remote: -----> Discovering process types
remote: Procfile declares types -> (none)
remote: Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote: Done: 87.3M
remote: -----> Launching...
remote: Released v3
remote: https://dyno-might.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/dyno-might.git
* [new branch] master -> master
Now you simply navigate to the URL provided when you created the heroku app (in this case http://dyno-might.herokuapp.com/) and within a matter of seconds your app will be up and running! Easy-peasy, right?
To deploy updates to your application, simply commit and push to the heroku
master branch again.
Now that we've deployed our first Commandbox/Heroku app, let's briefly discuss a few other considerations:
Persistent Shared Storage
In container-based deployments, your applications are meant to be created and destroyed with each edployment. This means that user-created assets will need to be managed separately from the file system of your application, or they will be destroyed on the next deployment. This consideration necessitates the use of a CDN storage network like S3. Coldbox has an SDK available for you to use in crafting your own persistent storage solutions, or you can roll your own solution using a variety of storage providers and even your own self-hosted CDN.
Database connectivity
By default, Heroku supports the use of of PostgreSQL database connections in your applications, which you can access through your "Database" menu item in the upper right-hand corner of the Heroku Dashboard. A variety of other SQL and No-SQL addons are available, as well, to allow for persistent data storage in your applications. Below are links to a variety of different database addons:
- ClearDB MySQL
- JawsDB for MariaDB
- SQL Server FreeTDS Pass-through Tutorial
- MongoDB via MongoLab
- Heroku Redis
- Additional Data Store Add-ons
From a deployment standpoint, you can manage your database URLs and credentials using the Heroku configuration and then pull those values in as environment variables in your Application.cfc to create the datasource like so:
heroku config:set DB_NAME='dyno_might' \
DB_USER='mighty' \
DB_PASSWORD='my53cUr3P455' \
DB_HOST='my.dbhost.com'
Then define the datasource in your Application.cfc:
var system = createObject( "java", "java.lang.System" );
var env = system.getEnv();
this.datasources["myDatasource"] = {
// required
type: 'mysql'
, host: env[ "DB_HOST" ]
, database: env[ "DB_NAME"]
, port: 3306
, username: env[ "DB_USER" ]
, password: env[ "DB_PASSWORD" ]
};
For PostgreSQL connections, using Heroku's built-in connectivity, a $DATABASE_URL
environment variable is already available. As long as you are connecting to the database from within your dyno, that connection information is already available, using a little Java to negotiate the connection string:
var system = createObject( "java", "java.lang.System" );
var env = system.getEnv();
this.datasources["myDatasource"] = {
type:'postgres',
connectionString: env["DATABASE_HOST"]
};
Advanced Configuration and Deployment
For advanced configuration and deployment options, Heroku allows for the use of an app.json
file with predeploy and postdeploy script hooks. An example schema and reference may be found here.
Deploying Alternate Branches
By default, Heroku listens for pushes to the master branch. If you would like to deploy and alternate branch, you will need to use a different syntax ending with :master
for your git push
command:
git push heroku development:master
### Considerations
Volume/Traffic
All files, including static assets, in your application will be served by the underlying CommandBox servlet container (RunWAR) and the assigned CFML engine. As such, deployments using the CommandBox build pack are primarily targeted towards low to medium traffic sites. For more robust deployments consider using a Tomcat/Lucee deployment or customize your own buildpack for your implementation.
#### Using Adobe CFML Engines
Because Adobe CFML Engines are closed-source and require licensing, you will need to deploy the entire ACF config directory with your application. The easiset way to do this is by setting the webConfigDir
setting in commandbox to reside within the root of your project:
box server set app.webConfigDir="./"
Then commit that directory to your repo, customize your configuration and licensing and deploy that directory along with your project. The downside of this is that you add about 200-300MB of files to your repo, which will slow down your deployment times.
Another option is to set the server configuration directory, ignoring everything except the the WEB-INF/lib
directory within that set of files. Then use app.json
to copy that directory during predeploy, and then stop the server during post-deploy, replace that directory within the generated config Directory, and restart the server. From an ease-of implementation standpoint, shipping the entire config directory provides the most simple solution.
Feel free to test deployments with the new buildpack and post issues or pull requests as you grow your own PaaS implementation.
Happy Coding!
Add Your Comment
(3)
Jan 06, 2017 13:01:22 UTC
by Didier LESNICKI
Dear Jon, Thanks a lot for this very inresting post. I have tried to follow exactly your instructions but I always receive this: (even when I upgrade to a more powerful dyno ) May be you could help me ? Best regards, Didier Lucee 4.5.3.020 Error (expression) Message invalid component definition, can't find component [coldbox.system.Bootstrap] Stacktrace The Error Occurred in /app/Application.cfc: line 24 22: // application start 23: public boolean function onApplicationStart(){ 24: application.cbBootstrap = new coldbox.system.Bootstrap( COLDBOX_CONFIG_FILE, COLDBOX_APP_ROOT_PATH, COLDBOX_APP_KEY, COLDBOX_APP_MAPPING ); 25: application.cbBootstrap.loadColdbox(); 26: return true;
Jan 06, 2017 16:28:51 UTC
by Jon Clausen
Hi Didier, That error usually occurs because your Coldbox framework isn't deployed with the application (or it's not in the main root, in which case you'll need to create a mapping in Application.cfc). If you've omitted those files from your repository, you can add the "box install" to your "scripts.postdeploy" in "app.json". See here for more information: https://devcenter.heroku.com/articles/app-json-schema. Feel free to reply with any additional questions.
Mar 28, 2020 21:02:42 UTC
by Francesco
Hi Jon, I've tried several times using this tutorial, but always get error h10 from heroku. I've also tempted using the button here: https://github.com/Ortus-Solutions/heroku-buildpack-commandbox The app is correctly deployed, but always crash. Do you have any suggestion?