Hey folks, after much tinkering, I got RedwoodJS deployed to Azure App Service. Not sure if this should go here, but here we go. Azure App Service is the hosting server service offered by Azure that works with my tech stacks. It technically runs your app in a docker container but makes all the configuration transparent to the user.
If you were attempting to configure your app to run node.js and push your code. By default, it will try to auto-build your application by running yarn workspace, which will fail. If you somehow were to get it to install, it would automatically run yarn build.
Azure offers a deployment process using Github Actions to install, build, and upload assets, but this process is broken. You can see issues with it here Deployment is *very* slow (nodeJS with publish profile) · Issue #60 · Azure/webapps-deploy · GitHub.
A custom deployment script is the best approach I have found to succeed. App Service uses a preconfigured Kudu build process to build your application, but you can generate and customize this file. And that was the route I took. You can find the guide here.
Create a .deployment file at the root of your project and add the following.
[config]
command = bash deploy.sh
Next, create a deploy.sh file and add the following.
#!/bin/bash
# ----------------------
# KUDU Deployment Script
# Version: 1.0.17
# ----------------------
# Helpers
# -------
exitWithMessageOnError () {
if [ ! $? -eq 0 ]; then
echo "An error has occurred during web site deployment."
echo $1
exit 1
fi
}
# Prerequisites
# -------------
# Verify node.js installed
hash node 2>/dev/null
exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment."
# Setup
# -----
SCRIPT_DIR="${BASH_SOURCE[0]%\\*}"
SCRIPT_DIR="${SCRIPT_DIR%/*}"
ARTIFACTS=$SCRIPT_DIR/../artifacts
KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"}
if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then
DEPLOYMENT_SOURCE=$SCRIPT_DIR
fi
if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then
NEXT_MANIFEST_PATH=$ARTIFACTS/manifest
if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then
PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH
fi
fi
if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then
DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot
else
KUDU_SERVICE=true
fi
if [[ ! -n "$KUDU_SYNC_CMD" ]]; then
# Install kudu sync
echo Installing Kudu Sync
npm install kudusync -g --silent
exitWithMessageOnError "npm failed"
if [[ ! -n "$KUDU_SERVICE" ]]; then
# In case we are running locally this is the correct location of kuduSync
KUDU_SYNC_CMD=kuduSync
else
# In case we are running on kudu service this is the correct location of kuduSync
KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync
fi
fi
# Node Helpers
# ------------
selectNodeVersion () {
if [[ -n "$KUDU_SELECT_NODE_VERSION_CMD" ]]; then
SELECT_NODE_VERSION="$KUDU_SELECT_NODE_VERSION_CMD \"$DEPLOYMENT_SOURCE\" \"$DEPLOYMENT_TARGET\" \"$DEPLOYMENT_TEMP\""
eval $SELECT_NODE_VERSION
exitWithMessageOnError "select node version failed"
if [[ -e "$DEPLOYMENT_TEMP/__nodeVersion.tmp" ]]; then
NODE_EXE=`cat "$DEPLOYMENT_TEMP/__nodeVersion.tmp"`
exitWithMessageOnError "getting node version failed"
fi
if [[ -e "$DEPLOYMENT_TEMP/__npmVersion.tmp" ]]; then
NPM_JS_PATH=`cat "$DEPLOYMENT_TEMP/__npmVersion.tmp"`
exitWithMessageOnError "getting npm version failed"
fi
if [[ ! -n "$NODE_EXE" ]]; then
NODE_EXE=node
fi
NPM_CMD="\"$NODE_EXE\" \"$NPM_JS_PATH\""
else
NPM_CMD=npm
NODE_EXE=node
fi
}
##################################################################################################################################
# Deployment
# ----------
echo Handling node.js deployment.
# 1. KuduSync
if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then
"$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh"
exitWithMessageOnError "Kudu Sync failed"
fi
# 2. Select node version
selectNodeVersion
# 3. Install npm packages
if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then
cd "$DEPLOYMENT_TARGET"
echo "Running yarn install"
eval yarn install
echo "Build!"
eval yarn rw build api
exitWithMessageOnError "yarn failed"
cd - > /dev/null
fi
##################################################################################################################################
echo "Finished successfully."
Most of this is the default stuff, the main change in the deploy process was to do yarn install instead of yarn workspace to get dev dependencies installed. Next was to run redwoods yarn rw build api.
After Kudu runs that custom deployment it will start the server inside the nodejs container automatically using yarn start. Updating the package.json and adding a script to run yarn rw build api is the next thing to do.
{
"private": true,
"scripts": {
"start": "yarn rw serve api"
},
...
}
Next I needed to have the API is use what ever port App Service wants it to use. Updating the redwood.toml file I needed to allow for it to set the port from the env.
...
[api]
port = "${PORT:8911}"
[browser]
open = false
The last thing I did was update the Github Action to automate the deployment to Azure.
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
# name: Build and deploy Node.js app to Azure Web App - redwood-goose
on:
push:
branches:
- master
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v2
with:
name: node-app
path: .
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v2
with:
name: node-app
- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'redwood-goose'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE}}
package: .
I completely removed the nodejs build phase of the original template and I just have the action pushing the code to Azure. From here Azure will run the custom Kudu deploys.sh and then start the server using yarn start which invokes yarn rw build api. From the dashboard on Azure I used the SSH tool to go into the app and run migrate to get the database going after configuring the URL.
There is a way to get Azure Static Webapps and App Service to play nice with each other that I want to look into. But that’s for another day.