Hosting Websharper/ASP.NET apps on Linux with Mono + nginx

F# + cheaper hosting = winning

One of the arguments often levelled against .NET web frameworks is that Windows hosting options are expensive compared to their Linux counterparts. Pricing aside, many people, myself included, also prefer the simplicity and flexibility of being able to quickly SSH into a box for administration rather than faffing around with remote desktop.

In the past Mono had something of a reputation for poor performance due to the primitive garbage collector. Mono 3.0 however ships with a new garbage collector called sgen which is much better. The Xamarin guys are doing a great job and it now seems ready for primetime.

Having recently been experimenting with Websharper, and being a big proponent of F#, I was keen to see if I could have the best of both worlds. Would it be possible, I wondered, to use Mono to host a Websharper app on Linux?

My initial attempts at installing Mono and F# proved somewhat fruitless because the mainstream Debian packages are hopelessly out of date. Fortunately some bright spark has uploaded some more recent ones onto launchpad which makes the process fairly straightforward.

Once that was done the rest was easy enough. I just copied a compiled Websharper site across from my Windows machine, fired up fastcgi-mono4, configured nginx to proxy the requests and hey presto, the page popped up! The same process should also work just fine for ASP.NET sites.

One small caveat: I did run into a websharper bug that was causing links to render incorrectly but that wasn’t too difficult to resolve.

Being a fan of automation (i.e incredibly lazy) I also created some vagrant provisioning scripts. This means you can be up and running with Ubuntu 13.04 64-bit Server hosting a Websharper site in minutes!


What the scripts do

  1. Download and install Mono 3.0.10 and F# 3.0
  2. Adds init.d script for fastcgi-mono4 (/etc/init.d/monoserve) – this also configures mono to use the new sgen garbage collector.
  3. Sets up nginx to point to fastcgi4-mono.
  4. Hosts the sample Websharper app in which is housed in /vagrant/www (this folder is shared between the guest VM and the host machine).

How to get started

  1. Install Virtualbox.
  2. Install Vagrant.
  3. Clone the provisioning scripts from my bitbucket account:
    git clone mono
  4. Launch the vagrant box:
    cd mono
    vagrant up
  5. Once everything has finished configuring it dumps out the boxes IP addresses to the console. Just point your browser to the eth1 IP address and you should see the site running!
  6. Replace the sample files in the

    folder with your own website.

  7. Profit!

I’ve also tried running the scripts on some cloud hosting rather than inside vagrant and they work great.

One-click website deployment using TeamCity, NAnt, Git and Powershell.


Until recently my organisation relied on FTP to push site updates. Whilst this certainly seems like a simple solution at first glance it soon becomes unmanageable. To name but a few of the difficulties:

  • For large sites re-uploading every file takes a long time, so developers tend to try and push individual files relating to their set of changes. Nobody really knows which version of the build is live and files soon get out of sync and the site collapses.
  • If something does go wrong things have to be restored from a backup, which again takes time.
  • Tracking down bugs is difficult it’s nigh-on impossible to correlate the live site to a particular build source control.
  • Web farms are particularly problematic because the files have to manually be pushed to multiple nodes.


We needed a deployment solution to fit the following requirements:

  • Bandwidth-friendly – deploys should only be transferring changes, not re-uploading the entire site.
  • Quick to switch between builds to minimise downtime
  • Easy to revert to previous builds
  • Able to easily deploy to multiple servers (web farms)
  • Platform-independent. We are primarily a Microsoft shop but didn’t want the deployment system to be tied to Visual Studio/ASP.NET.
  • Easy to deploy. Ideally it should be as simple as clicking ‘Build’ in Teamcity.
  • Ability to have multiple configurations, for example testing, staging and live.



The solution essentially boils down to the following:

  • Use NAnt to build our solutions and apply any configuration-specific changes (for example, web.config changes which need to be made for staging/live).
  • Use a version control system (git) to hold each successful build.
  • Have each server in the web farm frequently pull and apply the latest changes from the build server.

Flow diagram of deploy process


You will need the following tools installed:

On the build server:

  • NAnt
  • Git (My suggestion would be to install Git Extensions which includes everything you need plus a few nice GUI tools for windows users)

On each web server:

Step 1 – Build configurations

Create a ‘DeploymentOverrides’ directory in the root of your solution and a subdirectory for each configuration (development, testing, staging, live etc.) The idea is that the contents of the relevant folder will be copied over the top of each successful build. In the case of ASP.NET for example you might want to put a different web.config in each folder.

Step 2 – Create Git Repositories for the build outputs

On the build server you’ll need to create git repositories to hold the successful outputs from each build. For example:

mkdir F:\GitRepositories\Testing\MyWebsite
mkdir F:\GitRepositories\Staging\MyWebsite
mkdir F:\GitRepositories\Live\MyWebsite

cd F:\GitRepositories\Testing\MyWebsite
git init

cd F:\GitRepositories\Staging\MyWebsite
git init

cd F:\GitRepositories\Live\MyWebsite
git init

Step 3 – NAnt build script

NAnt is a great tool to automate builds. It uses an XML configuration to describe the different ‘targets’. The following example script holds a few different configuration options (Local, Testing, Staging and Live).

When NAnt is run the solution is compiled using MSBuild, the overrides are copied over from the relevant subdirectory of DeploymentOverrides, and the resultant output is committed its corresponding git repository.

By convention this file should be called and placed in the root of your solution.

<?xml version="1.0"?>
<project name="YourProjectName" default="default">

    Build configurations:
    Local        - For local builds/testing
    Testing    - Teamcity deploy to development server
    Staging - Teamcity deploy to staging server
    Live        - Teamcity deploy to live servers

  <property name="solutionFilename" value="${project::get-name()}.sln" />

    <!-- This is the source folder. i.e. set this to the folder containing the output of the build -->
  <property name="msBuildOutputFolder" value="Website" />

  <!-- manual targets to override above default -->
  <target name="local">
      <property name="deployTarget" value="local" />
  <target name="testing">
    <property name="deployTarget" value="testing" />
  <target name="staging">
      <property name="deployTarget" value="staging" />
  <target name="live">
      <property name="deployTarget" value="live" />

  <target name="setEnvironmentalProperties">
    <!-- Set deployment target to local if not explictly specified -->
    <if test="${not property::exists('deployTarget')}">
          <fail message="Must specify valid build target."/>

    <call target="setEnvironmentalPropertiesLocal" if="${deployTarget=='local'}" />
  <call target="setEnvironmentalPropertiesTesting" if="${deployTarget=='testing'}" />
  <call target="setEnvironmentalPropertiesStaging" if="${deployTarget=='staging'}" />
  <call target="setEnvironmentalPropertiesLive" if="${deployTarget=='live'}" />


  <target name="setEnvironmentalPropertiesLocal">
    <!-- Web config settings -->
    <property name="deploymentOverridesSource" value="DeploymentOverrides/Local" />

    <!-- This is the destination git repository used to hold successful builds -->
    <property name="gitRepository" value="SuccessfulBuilds/Local" />

    <target name="setEnvironmentalPropertiesTesting">
    <!-- Web config settings -->
    <property name="deploymentOverridesSource" value="DeploymentOverrides/Testing" />

    <!-- This is the destination git repository used to hold successful builds -->
    <property name="gitRepository" value="F:/GitRepositories/Testing/MyWebsite" />

    <target name="setEnvironmentalPropertiesStaging">
    <!-- Web config settings -->
    <property name="deploymentOverridesSource" value="DeploymentOverrides/Staging" />

    <!-- This is the destination git repository used to hold successful builds -->
    <property name="gitRepository" value="F:/GitRepositories/Staging/MyWebsite" />

  <target name="setEnvironmentalPropertiesLive">
        <!-- Web config settings -->
    <property name="deploymentOverridesSource" value="DeploymentOverrides/Live" />

    <!-- This is the destination git repository used to hold successful builds -->
    <property name="gitRepository" value="F:/GitRepositories/Live/MyWebsite" />

  <target name="default" depends="compile" />

  <target name="compile">
    <msbuild project="${solutionFilename}">
        <property name="Configuration" value="Release"/>

  <target name="deploy" depends="compile, addToGit" />

  <target name="updateSettings" depends="setEnvironmentalProperties">
      <!-- Copy config files for this build configuration over the output from the build -->
      <copy todir="${gitRepository}" overwrite="true">
      <fileset defaultexcludes="false" basedir="${deploymentOverridesSource}">
          <include name="**/*" />
          <exclude name=".do-not-delete" />

    <!-- ************************************************************** -->
    <!-- *** Tasks to add successful build output to git repository *** -->
    <!-- ************************************************************** -->

  <target name="copyBuildOutputToGit" depends="setEnvironmentalProperties">

  <!-- Delete entire working folder from git leaving only the main .git folder behind -->
      <fileset defaultexcludes="false" basedir="${gitRepository}">
          <include name="**/*" />
          <exclude name=".git" />
          <exclude name=".git/**" />

  <!-- Copy entire output of successful build into the git working folder -->
  <copy todir="${gitRepository}">
      <fileset defaultexcludes="false" basedir="${msBuildOutputFolder}">
          <include name="**/*" />

  <target name="addToGit" depends="setEnvironmentalProperties, copyBuildOutputToGit, updateSettings">
  <!-- Commit the contents of the working folder to the git repository -->

  <!-- Write timestamp of build into a file. Useful for reference but also ensures there is always
       a change to commit. This way if commit fails we know there was actually an error.
       (git commit fails if the index is empty)
  <tstamp />
  <echo message="NAnt build successful at ${}" file="${gitRepository}/build.log" append="false"/>

  <!-- This check is necessary so that we don't inadvertently end up checking in files
  to the source code repository if the build repository doesn't exist! -->
  <if test="${not directory::exists(gitRepository + '/.git')}" >
         <fail message="Git repository for build output is not initalised!"/>

  <!-- Stage files to git index -->
  <exec append="true" workingdir="${gitRepository}" program="git">
      <arg value="add" />
      <arg path="${gitRepository}/." />

  <!-- Commit files -->
  <exec append="true" workingdir="${gitRepository}" program="git">
      <arg line="commit" />
      <arg value="-a" />
      <arg value="-m" />
      <arg value="Successful build: ${}" />

  <!-- Update server info (for HTTP repositories) -->
  <exec append="true" workingdir="${gitRepository}" program="git">
      <arg line="update-server-info" />


Step 4 – Set up Teamcity (optional)

You will want to create a teamcity build for each different configuration (Testing, Staging, Live etc). Be sure to choose nant as the runner and set the target appropriately.

You can of course skip this step and simply call nant manually from the command line if you wish.

Configuration settings for NAnt within teamcity

Step 5 – Expose the git repositories

There are various ways to do this, but since we are only going to be pulling from the repository then setting it up as HTTP is the simplest method and provides some simple security (basic authentication).

I simply pointed create a new IIS website with its root at F:\GitRepositories and enabled basic authentication. Depending on your security requirements etc you may want to contemplate using SSL or other means of exposing the repository to the web servers (VPN, SCP, Samba etc).

Step 6 – Clone the git repositories onto the web servers

On the web servers, you will need to clone the appropriate git repository from the build server into the root each of your IIS applications, for example:

git clone

Step 7 – Set up the servers to regularly pull the latest updates

For simplicity, and because we needed to be able to spin up servers on demand without adjusting the configuration,we chose to have the web servers continuously poll for updates from the build server. This means that the build server doesn’t need to know anything about the web servers.

A push-based system might be more efficient (less network chatter, no need for polling) but it is left as an exercise for the reader!

In order to pull updates we just need to tell git to fetch and merge the latest changes. The following powershell script will automate this process and automatically fetch updates for every git repository. As a bonus, it also writes status updates to the windows event log.

# Perform fetch on all git repositories immediately beneath F:\git-deploy-repos
cd F:\git-deploy-repos
dir | %{
    if (test-path "$_\.git")
        echo "Performing fetch on : $_"
        cd $_
        git fetch
        # Writing an event
        $EventLog = New-Object System.Diagnostics.EventLog('Application')
        $EventLog.MachineName = "."
        $EventLog.Source = "Fetch-Updates"
        if ($?)
        echo "Fetch on $_ completed"
        $EventLog.WriteEntry("Successfully fetched updates for $_","Information", $EventID)
                echo "Applying changes to dev build: $_"
                git clean -f -d
                git reset --hard head
                git merge origin/master
                if ($?)
                    echo "Changed succesfully applied to: $_"
                    $EventLog.WriteEntry("Successfully applied updates for build $_","Information", $EventID)
                    echo "Failed to apply changes to: $_"
                    $EventLog.WriteEntry("Failed to apply updates for build $_","Error", $EventID)
        echo "Fetch on $_ failed"
        $EventLog.WriteEntry("Failed to fetch updates for $_","Error", $EventID)
        cd ..

Set up a windows scheduled task to run this powershell script every few minutes and you are done 🙂

Step 8 – Take it for a spin!

Now all the configuration is done deployment is as simple as clicking ‘build’ in teamcity. This will build the solution, copy in any configuration-specific overrides (web.config etc.), and add it to the local git repository. The next time the scheduled task runs on each of the web servers the changes will be pulled over the network and then applied to the live website.

If you need to revert to the previous build you can simply run the following command on each of the web servers.

git reset --hard HEAD^

Of course you can revert to any build you like! Git Extensions really comes into its own in this scenario because it means you can easily visualise the timeline and view changes between different builds. Switching between builds a couple of clicks!

Git Extensions diff screen showing changes between deploys

See also

Rob Conery has also proposed a git-based solution for website deployments which is worth a read: