Deploying Jekyll to Windows Azure App Services

Written by Khalid Abuhakmeh
7
This post is days old.

As the development team at Ritter Insurance Marketing, we are leaning more on static site generation than ever. We have chosen Jekyll as our static site generator of choice. We actually host this blog on GitHub pages, but there are other marketing sites we manage that need to go through a more rigorous deployment cycle, Dev. to QA then to Prod, which doesn’t fit well on GitHub. We also utilize Windows Azure for our production environment. To keep our apps consistent, I wanted to figure out how to deploy a Jekyll site from GitHub to Windows Azure App Services (Web Apps).

Step 1 : Setup GitHub Deployment

This step is pretty simple, and I am not going to rehash it here. If you don’t know how to do deploy via GitHub I recommend this post from Microsoft.

Step 2 : Setup Scripts

I actually started with Scott Hanselman’s example but found a few issues with it. The two issues were located in the getruby.cmd file.

First, I updated line #1 of the command file. The previous first line of ECHO ON was spitting out each line as if it were a command into the Azure Console output, which didn’t end well for the deployment. Switching to the following fixed this problem.

@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

Secondly, I updated the creation script of the tools folder to be more defensive. Previously it couldn’t find the tools folder to move into and create the r folder where Ruby will be installed.

REM I am in the repository folder
pushd D:\home\site\deployments
if not exist tools md tools
cd tools
if not exist r md r
cd r
if exist ruby-2.2.4-x64-mingw32 goto end

Finally, I upgraded the version of Ruby to the latest installer (2.2.4). All issues I had with the original script should now be fixed in the files below. Now that the scripts are updated, you will need to create four files in your git repository, if they are not already there.

  1. .deployment
  2. getruby.cmd
  3. deploy.cmd
  4. Gemfile

.deployment

The .deployment file will be seen by Kudu and run deploy.cmd.

[config]
command = deploy.cmd

.deploy.cmd

The deploy.cmd file does two things:

  • Call getruby.cmd and ensures Ruby is installed
  • Kudu sync the files from Jekyll under the _site directory
@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

:: ----------------------
:: KUDU Deployment Script
:: Version: 1.0.6
:: ----------------------

:: Prerequisites
:: -------------

:: Verify node.js installed
where node 2>nul >nul
IF %ERRORLEVEL% NEQ 0 (
  echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
  goto error
)

:: Setup
:: -----

setlocal enabledelayedexpansion

SET ARTIFACTS=%~dp0%..\artifacts

IF NOT DEFINED DEPLOYMENT_SOURCE (
  SET DEPLOYMENT_SOURCE=%~dp0%.
)

IF NOT DEFINED DEPLOYMENT_TARGET (
  SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
)

IF NOT DEFINED NEXT_MANIFEST_PATH (
  SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest

  IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
    SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
  )
)

IF NOT DEFINED KUDU_SYNC_CMD (
  :: Install kudu sync
  echo Installing Kudu Sync
  call npm install kudusync -g --silent
  IF !ERRORLEVEL! NEQ 0 goto error

  :: Locally just running "kuduSync" would also work
  SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
)
ECHO CALLING GET RUBY

call :ExecuteCmd "getruby.cmd"

ECHO WE MADE IT

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Deployment
:: ----------

echo Handling Basic Web Site deployment.

:: 1. KuduSync
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
  call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%/_site" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
  IF !ERRORLEVEL! NEQ 0 goto error
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Post deployment stub
IF DEFINED POST_DEPLOYMENT_ACTION call "%POST_DEPLOYMENT_ACTION%"
IF !ERRORLEVEL! NEQ 0 goto error

goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal
set _CMD_=%*
call %_CMD_%
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
exit /b %ERRORLEVEL%

:error
endlocal
echo An error has occurred during web site deployment.
call :exitSetErrorLevel
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal
echo Finished successfully.

getruby.cmd

The getruby.cmd file will download the latest version of Ruby and install it on your App Service instance. In addition to that, it will install all the dependencies in your Gemfile which is below. Finally, it will execute the jekyll build command via bundler.

@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

REM Put Ruby in Path
REM You can also use %TEMP% but it is cleared on site restart. Tools is persistent.
SET PATH=%PATH%;D:\home\site\deployments\tools\r\ruby-2.2.4-x64-mingw32\bin

REM I am in the repository folder
pushd D:\home\site\deployments
if not exist tools md tools
cd tools
if not exist r md r
cd r
if exist ruby-2.2.4-x64-mingw32 goto end

echo No Ruby, need to get it!

REM Get Ruby and Rails
REM 64bit
curl -o ruby224.zip -L https://bintray.com/artifact/download/oneclick/rubyinstaller/ruby-2.2.4-x64-mingw32.7z?direct
REM Azure puts 7zip here!
echo START Unzipping Ruby
SetLocal DisableDelayedExpansion & d:\7zip\7za x -xr!*.ri -y ruby224.zip > rubyout
echo DONE Unzipping Ruby

REM Get DevKit to build Ruby native gems  
REM If you don't need DevKit, rem this out.
curl -o DevKit.zip http://cdn.rubyinstaller.org/archives/devkits/DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe
echo START Unzipping DevKit
d:\7zip\7za x -y -oDevKit DevKit.zip > devkitout
echo DONE Unzipping DevKit

REM Init DevKit
ruby DevKit\dk.rb init

REM Tell DevKit where Ruby is
echo --- > config.yml
echo - D:/home/site/deployments/tools/r/ruby-2.2.4-x64-mingw32 >> config.yml

REM Setup DevKit
ruby DevKit\dk.rb install

REM Update Gem223 until someone fixes the Ruby Windows installer https://github.com/oneclick/rubyinstaller/issues/261
curl -L -o update.gem https://github.com/rubygems/rubygems/releases/download/v2.2.3/rubygems-update-2.2.3.gem
call gem install --local update.gem
call update_rubygems --no-ri --no-rdoc > updaterubygemsout
ECHO What's our new Rubygems version?
call gem --version
call gem uninstall rubygems-update -x

popd

:end

REM Need to be in Reposistory
cd %DEPLOYMENT_SOURCE%
cd

call gem install bundler

ECHO Bundler install (not update!)
call bundle install

cd %DEPLOYMENT_SOURCE%
cd

ECHO Running Jekyll
call bundle exec jekyll build

REM KuduSync is after this!

Gemfile

If you don’t already have a Gemfile, create one that looks like the following.

source 'https://rubygems.org'

gem 'github-pages'

Deploying

Once these files are committed to your repository, Kudu should deploy your site. Be patient during the first deployment, as it may take up to 3 minutes to download and install Ruby. I found on my instance, after the Ruby installation, that generating a simple Jekyll site took less than 10 seconds. Obviously your results will vary depending on your site.

Conclusion

It took a little work to get this right. I thought the Azure CLI would have a Jekyll deployment template all ready for use, but it didn’t. The nice thing about this deployment script is it will work for any Jekyll setup and is easy to modify if you need to add other steps. If you need more Ruby dependencies please use the Gemfile and it should just work. I hope you found this post helpful.

Side note: running the deploy.cmd from the Azure Console does not guarantee the environment variables will be the same as when Kudu runs the deploy.cmd. This can make it difficult to debug issues.

Suggested reading

Comments