One does not simply run Node.js on Microsoft Azure

9

We've been running Node.js on Microsoft Azure for about a year now on mediocre.com and recently launched meh.com using the same tech stack. Thought I'd share some of the tips, tricks, and things we've learned along the way.

You might be asking, "Node.js on Azure? Why?" I get this from time to time. I had a variety of reasons for going this route. Here's a big one: I've had a career's worth of experience building sites using .NET on AWS and wanted to try something different.

Let's set aside the reasoning behind running Node.js on Microsoft Azure for now and dive into the things you're gonna want to know if you end up going the same direction. If you're looking for the TL;DR here it is in meme form:

Docs

First, if you're going to run Node.js on Microsoft Azure you should read the docs at the Azure Node.js Dev Center. Over the past 18 months I've read them cover-to-cover dozens of times. They haven't changed much during that time which makes me wonder how much effort Microsoft is putting into Node.js on Azure these days. It's a pretty good set of docs though. They're certainly not comprehensive, but they're good getting started and tutorial material.

Support

I'll keep this part short. We're a Microsoft BizSpark member and I expected more help when I needed it. If you want to read more I've been fairly vocal about the mediocre support we've experienced.

Cloud Services vs Web Sites vs Virtual Machines

There's a few ways you can run Node.js apps on Azure. I would recommend using "Web Sites" when you can. It's the part of Azure where Microsoft is doing the most management for you, but you also have the least control.

Everything I'm going to cover here will be about "Cloud Services". We decided to run our public facing web applications and internal REST services using Cloud Services so that we could keep them inside a Virtual Network for security purposes.

Development

We do all our development on Mac OS X. The main reason I'm pointing this out is because Microsoft has created some tooling and support for Node.js inside Visual Studio which we don't use. Things might be easier with Visual Studio.

Build and deploy a Node.js application to an Azure Cloud Service

Once you're ready to get started working on a Node.js Azure Cloud Service, you'll probably land at this documentation. It's over two years old now, but it's where we got started.

The first thing you'll notice is that this documentation is written for Windows and PowerShell (not Mac). I created a Windows VM in Azure so I could run through the tutorial, then I pushed the files it created to GitHub and continued development on my Mac.

Follow all the steps in the tutorial and you'll end up with a working Node.js app running on an Azure Cloud Service.

IISNode

I mentioned the Azure docs for Node.js are about two years out of date. So are the PowerShell scripts and the dependencies they setup. You're going to want to update some things.

When you run Node.js on an Azure Cloud Service you're running Node.js on Windows in IIS using IISNode. The version that's installed by default is v0.1.21 which is over two years old. You'll want to upgrade to something like v0.2.14 before your app starts taking on traffic and runs into memory heap corruption issues like we did.

node.exe

The version of node.exe that's installed by default is really, really old (v0.6.20). You'll probably want to use the latest v0.10.x version. Here's how we got this to work.

iisnode.yml vs Web.cloud.config

IISNode supports YAML configuration through an iisnode.yml file. It also supports configuration through the Web.cloud.config file. Since you need to have a Web.cloud.config file for IIS we do all our configuration there and ditched the iisnode.yml file to avoid any confusion.

Here's IISNode settings we changed in our Web.cloud.config file:

  • debuggingEnabled="false" - turned off debugger for performance
  • devErrorsEnabled="false" - turned off to get HTTP 5xx responses on errors instead of HTTP 200
  • logDirectory="logs" loggingEnabled="true" - setup logging to a text file which has come in handy in certain debug scenarios
  • node_env="production" - obvious
  • nodeProcessCommandLine="D:\node.exe" - use the Node.js binary that we placed on the D: drive in our deployment package
  • nodeProcessCountPerApplication="0" - in case we run on a machine that has multiple processors tells IISNode to startup a node.exe process for every processor
  • promoteServerVars="HTTPS,REMOTE_ADDR" - Tells IISNode to to forward some IIS server variables in the form of HTTP headers to each request. We use the HTTPS server variable to help us with HTTP/HTTPS endpoints. As I answered on Stack Overflow, the trick is to let IIS handle the SSL termination for you so that your Node.js app only listens on HTTP (using process.env.PORT). We use the REMOTE_ADDR server variable because the X-Forwarded-For header doesn't work correctly out of the box and we patch it with this bit of middleware

Error Pages

IIS loves, loves, loves to be all up in your error pages.

But we have a little troll just waiting to sing his heart out when something goes terribly wrong

Here's what we did to tell IIS to chill, Winston... we'll handle error pages:

Deployment

This is the worst part of running Node.js on a Microsoft Azure Cloud Service in my opinion. Here's my top three reasons why:

  • There's no way to deploy to an Azure Cloud Service from a Mac. The Cross Platform Azure CLI doesn't have this feature. The Cloud9 guys had something that no longer works with recent versions of Node. Your only option is to use PowerShell on Windows.
  • The Publish-AzureServiceProject PowerShell command occasionally fails to build the "cloud_package.cspkg" that is uploaded to Azure during a deployment. You won't get a meaningful error message. You'll turn on the "Verbose" flag and learn nothing from it. I'll save you some time and tell you what's happening. Node.js apps are typically built using lots and lots of tiny, but highly compatible modules that are managed by NPM. Eventually, you'll use a module from NPM in your app that depends on another module which depends on another module which depends on another module. It's very easy for this to build a dependency graph of modules on disk whose file paths end up being greater than 260 characters which is longer than Windows can handle (even the latest version). I have two workarounds. First, run "npm dedupe" in your deployment script (https://gist.github.com/freshlogic/1e9fe62aa0d160a9ef93#file-gistfile1-txt-L19). Second, identify which modules end up creating the longest file paths on disk and install them at the root of your project before you install other modules (https://gist.github.com/freshlogic/1e9fe62aa0d160a9ef93#file-gistfile1-txt-L12-L14).
  • It's slow. In our experience deployments usually take 15-30 minutes.

Final Thoughts

Can't say I'd recommend Node.js on Azure Cloud Services to other devs. As you can see, this was a pain in the ass to get all figured out. That said, we do enjoy working with other features of Azure. My personal favorite is the Azure Service Bus which is fantastic and we use it a ton. I wish Microsoft supported Azure Web Sites inside a Virtual Network the same way AWS supports Elastic Beanstalk inside a VPC.

Hoping we'll see more Node.js support on Microsoft Azure in the future, until then I hope these tips and tricks might save some devs a few hours.