Deploying Node to IIS with Teamcity and Octopus

I’ve been building a simple Node website as part of a side project, mainly as a learning exercise. The app itself is still pretty basic, but I’m at a point where I wanted to start thinking about deployment. I have an existing continuous integration setup with Teamcity and Octopus which has worked pretty seemlessly for my .Net projects, so I figured I’d see if I could shoehorn a Node app into this pipeline.

I’ve used NPM with previous Teamcity projects and we’ve just run ‘npm install’ and ‘npm test’ through a command line step, so I was pretty comfortable with that. But those projects were always hosted with ASP .Net and so we used standard Octopack Nuget package and IIS deployment. With a Node/Express app this wasn’t going to be an option.

So the initial questions I needed to ask were:

  • How do I package a node application and push it to an Octopus feed?
  • How do I host a node application in IIS?

Packaging a Node application in Teamcity and publishing it to Octopus

Thankfully, Octopus have long since provided support for Node applications and have provided a package octopackjs for this very purpose. The published documentation suggests either Gulp or Grunt as a task runner and I tend to prefer Gulp (due it to being less configuration heavy) so I opted for this.

The starter documentation suggests using ‘gulp-bump’ to manage the package version, but this isn’t ideal when you’re deploying through a CI server like Teamcity because all of your deployments occur as part of the build process and none of the bumps get checked in – meaning you don’t actually get neatly incremented versions.

The other issue with the starter version is that it requires your API Key and build server to be stored in plaintext in your Gulpfile – not ideal for a number of reasons, not least of which is security.

Thankfully, the solution to both of these problems is to inject parameters from your Teamcity configuration – both the API key and the Octopus URL can be constants, while the package version should be the build number.

After a bit of searching I found a neat way of handling command line parameters in a gulp file using the minimist package; it parses the arguments from process.argv into an object with named parameters for ease of access. As a result, my final gulpfile looked like this:

var gulp = require('gulp');
 var bump = require('gulp-bump');
 var octo = require('@octopusdeploy/gulp-octo');
 var minimist = require('minimist');

var defaults = {};
 var options = minimist(process.argv.slice(2), defaults);

gulp.task('bump', function () {
 return gulp.src('./package.json')
 .pipe(bump({ version: options.version }))
 .pipe(gulp.dest('./'));
 })

gulp.task('publish', ['bump'], function () {
 return gulp.src(['**/*', '!gulpfile.js'])
 .pipe(octo.pack())
 .pipe(octo.push({ apiKey: options.apiKey, host: options.host }));
 })

(Note that I’m not yet compiling my Javascript – my app is very much in MVP exploratory phase – but in most cases you’d be likely to have at least a ‘build’ task in here too).

To avoid needing to install the gulp-cli package, I add the script to my package.json:

"scripts": {
 ...
 "publish": "node ./node_modules/gulp/bin/gulp.js publish"
 }

Actually passing in the arguments from Teamcity requires a slightly odd bit of syntax, with an additional leading ‘–‘ to signify args to be passed to the node script. Ultimately though it’s a simple command line step in the build pipeline:

npm run publish -- --apiKey=[OCTOPUS-API-KEY] --host=http://myoctopusserver.com --version=%system.build.number%

Hosting a Node application in IIS

With the code above I can push my package to Octopus and from there I can just deploy straight to IIS as I would any other .Net application. The next challenge, however, is getting the application to run once it is deployed.

IIS (as far as I know) doesn’t know how to run a Node application by default, so you need to install a plugin  iisnode.  It’s packaged as an installer available for X86 or X64 and is compatible with any version of IIS greater than 7. I was plugging it in to IIS 8.5.

After downloading an installing the plugin on your web server, restart IIS (I’m not sure if this is necessary, but it seemed like a good thing to do). Then in your Node project, you need to tell IIS how to treat it.

In my case, this required three bits of config:

1) Tell IIS that this is a Node app

Create a web.config file in your root application folder, and add a handler mapping for your main application file:

<configuration>
 <system.webServer>
  <handlers>
   <add name="iisnode" path="src/app.js" verb="*" modules="iisnode" />
  </handlers>
 </system.webServer>
</configuration>

This tells IIS that any request for src/app.js mapped to this web application should be handled using the iisnode module we installed earlier.

2) Tell IIS that it needs to redirect requests to the main application entry point

Of course, people visiting our site aren’t actually going to be visiting src/app.js. In fact, there could be any number of different endpoints configured for Express. We want to ensure that all application traffic (except for static files – see below), gets routed through our main application entry point.

For this, we need the IIS Url Rewrite module installed, then we can add a rewrite rule to our web.config:

<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name=”myapp”>
<match url=”/*” />
<action type=”Rewrite” url=”src/app.js” />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

Now any incoming request will be handled by our node app.

3) Tell IIS about static content that it shouldn’t redirect to the main application entry point

There are some requests that we don’t want to handle through node. Unfortunately, the

snippet app.use(express.static(‘public’));

doesn’t seem to propagate up to IIS, so we need to handle assets via URL rewrite as well. We can add an additional rewrite rule such as the rules section of our config now looks like:

 <rules>
  <rule name="public" stopProcessing="true">
   <match url="(.*)" />
    <conditions logicalGrouping="MatchAny">
     <add input="{PATH_INFO}" pattern="css/.*" />
     <add input="{PATH_INFO}" pattern="fonts/.*" />
     <add input="{PATH_INFO}" pattern="images/.*" />
     <add input="{PATH_INFO}" pattern="lib/.*" />
     <add input="{PATH_INFO}" pattern="scripts/.*" />
    </conditions>
    <action type="Rewrite" url="public/{R:1}" />
   </rule>
   <rule name="myapp">
    <match url="/*" />
    <action type="Rewrite" url="src/app.js" />
   </rule>
</rules>

Now any incoming request in subfolders css, fonts, images, lib or scripts will be handled as a static asset. This feels a little clumsy to me, so I’m curious to know if there’s a better way of handling it, but as I wanted to timebox this exercise I didn’t quite get far enough to improve on this solution.

The result though works perfectly – a fully functional Node application deployed via Teamcity and Octopus to IIS.

mdsomerfield

Share

Leave a Reply

Your email address will not be published. Required fields are marked *