TL/DR - Use Azure Devops (or your preferred build platform) to include the Node executable it uses to build your app inside the zip package used to deploy your Azure Functions application.
Azure Functions (AF) presents a compelling sales pitch. They've built a platform where you can write code that they'll run. They'll handle the scaling for you, and on their consumption plan, you only pay for the actual server time you use. This can drastically reduce costs for running your applications in the cloud. As long as you are able to work within the confines of the platform, everything is awesome. If you're a .NET/C# developer, AF presents a compelling platform.
Unfortunately, the picture isn't currently (April 2019) quite as rosy for developers who wish to use other programming languages/platforms. The primary Azure Functions product/platform is built on top of the App Service platform, which presents some issues for non .NET developers. In particular, you are tied to the exact version of your programming platform's runtime that the AF team ships. I ran in to this issue recently while building a NodeJS-based Azure Functions application for a conference talk. Let's look at how I solved my particular issue.
Unfortunately, I developed my application locally never realizing there was going to be an issue, as the local development environment for Azure Functions uses your locally installed copy of NodeJS. I run on a 64-bit Windows 10 (and I would guess that most Windows developers in 2019 run on 64-bit builds of Windows), and thus I have the 64-bit NodeJS build installed. When I finished developing my demo I tried to push it to Azure Functions using the simple Github based deployment model, where the AF platform handles running npm install for me. I was flummoxed when npm install kept failing saying a dependency required a 64-bit version of Node. After some research, I realized that Sharp was the dependency that was the issue.
Fast-forward to this spring. Marie Hoeger from the AF team had worked with me to come up with some solutions on how to bring my own Node executable, but I didn't really have much success with these tips until she and I worked side-by-side on the issue at the MVP Summit in March. This was in part due to the instructions not being 100% clear for me, and also due to an unrelated issue causing me to go on a side-track with my app for a while. The eventual solution we used was what she proposed in the GitHub issue thread on February 19.
Specifically, these are the steps:
- In Kudu, which will be at
[sitename].scm.azurewebsites.net, go to Debug Console.
- Drop the node.exe you want to use in
D:\home(can be in root, can be nested)
- Take note of the path (ex:
- Add the AppSetting
languageWorkers:node:defaultExecutablePathand set it to the path (from before) to your
Note that you have to remove the
.exe from executable name when writing it to the Appsetting. So
So you can use that, but that means you have to deploy your
node.exe file by hand. So there has to be a better way. While we were trying to figure out the unrelated issue (which turned out to be an errant
app_offline.html file), Marie suggested I copy
node.exe to the folder with my function app and push that to GitHub. We could then point to that executable. At the time, this didn't work due to the
app_offline.html file, so we gave up on it. When we got everything working, we were using the exact strategy she had suggested in February, so the suggestion was forgotten for a few weeks.
But yesterday, I was playing with Azure Devops trying to get my custom Devops build (YAML-based, of course!) for my application working. The first thing I do in my build is to install Node 10.x
- task: NodeTool@0 displayName: 'Use Node 10.x' inputs: versionSpec: 10.x
In AzureDevops, this will pull down the newest x64 build of Node for the versionSpec specified. More information is located here.
At some point, the thought struck me... I often use the
which command in Bash to determine where the executable is coming from. Why couldn't I use that command to find the Node executable and copy it from wherever it is installed on the build agent to the folder with my application? This would allow me to always use the exact same executable for installing dependencies and running my application. This is important, as I've found that sometimes Sharp can be temperamental about being installed using a different major version of Node from what it is running. It would also remove the manual process of deploying the Node executable in Kudu.
I updated the
languageWorkers:node:defaultExecutablePath Appsetting to
D:\home\site\wwwroot\node as my Node executable would now be located at
I then added a Bash step to my build definition, making sure this command is before the Archive Files step:
- bash: 'cp "$(which node.exe)" .' workingDirectory: 'function-app' displayName: 'Copy node.exe for deployment'
In my case, my function app is not located at the root of my Git repo, but in a folder called
function-app, this is why the working directory for the command must be set.
I then ran my build and deployed it using Azure Devops. I'm now able to use a 64-bit version of Node with my application, and I ensure that it is the exact same version of Node that was used to install my dependencies. I believe this pattern will work for any platform where you want to use a specific version of your platform's executable, or at least override the default version that the Azure Functions platform uses. You would need to change the
languageWorkers:[YOUR_LANGUAGE_HERE]:defaultExecutablePath appsetting to match whatever language worker you are using, though.
I hope this can help others with similar situations.