«Running .Net on heroku

Recently I decided to try and get Nancy up and running on Heroku, heres a brief gist of things I did to get it up and working.

The buildpack

There are a few buildpacks for mono2.x around on the internet but I was aiming for Mono 3 this turned out to be the longest part of the whole process.

I tried to use vulcan to create a buildpack but kept hitting issues so ended up heroku run bash'ing, building Mono 3 and netcat'ing it over, its available here. I ended up throwing Nuget in there too. Heroku have some pretty good documentation on the process here. My compile script is pretty hacked together but seems to be holding up fine.

To use the build pack, pass it in with heroku create on a project

heroku create projname --buildpack https://github.com/aktowns/mono3-buildpack.git

The buildpack looks for a solution file (.sln) in the root directory, and if it detects a packages.config it will run Nuget and attempt to install the packages to packages/ this syncs up with the way monodevelop nuget addin stores downloaded packages.

The .gitignore file

When committing files to the repo, I used the following .gitignore to not push up compiled binaries or packages as the buildpack should manage all of that.

projdir/bin
projdir/obj
packages/*
*.userprefs

Nancy

Nancy is a Sinatra inspired framework for building web applications. Getting nancy setup with F# was pretty straight forward, the only issue initially was finding the right host to bind to, until i discovered binding to localhost allows connections intended for any host (from the HTTP Host: header).

Make sure to include Nancy and Nancy.Hosting.Self using monodevelop-nuget-addin.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
module viewfiles.Main

open System
open Nancy.Hosting

type DemoApp () =
    inherit NancyModule()
    do
        let Get = base.Get
        Get.["/"] <- fun parameters -> "Hello from F#/Nancy on Heroku!" :> obj

[<EntryPoint>]
let main args = 
    let env_port = Environment.GetEnvironmentVariable("PORT")
    let port = if env_port = null then "1234" else env_port

    let nancy_host = new Nancy.Hosting.Self.NancyHost(new Uri("http://localhost:" + port))
    nancy_host.Start()
    while true do Console.ReadLine() |> ignore
    0
namespace viewfiles
module Main

from viewfiles
namespace System
Multiple items
type DemoApp =
  inherit obj
  new : unit -> DemoApp

Full name: viewfiles.Main.DemoApp

--------------------
new : unit -> DemoApp
type obj = Object

Full name: Microsoft.FSharp.Core.obj
Multiple items
type EntryPointAttribute =
  inherit Attribute
  new : unit -> EntryPointAttribute

Full name: Microsoft.FSharp.Core.EntryPointAttribute

--------------------
new : unit -> EntryPointAttribute
val main : args:string [] -> int

Full name: viewfiles.Main.main
val args : string []
val env_port : string
type Environment =
  static member CommandLine : string
  static member CurrentDirectory : string with get, set
  static member CurrentManagedThreadId : int
  static member Exit : exitCode:int -> unit
  static member ExitCode : int with get, set
  static member ExpandEnvironmentVariables : name:string -> string
  static member FailFast : message:string -> unit + 1 overload
  static member GetCommandLineArgs : unit -> string[]
  static member GetEnvironmentVariable : variable:string -> string + 1 overload
  static member GetEnvironmentVariables : unit -> IDictionary + 1 overload
  ...
  nested type SpecialFolder
  nested type SpecialFolderOption

Full name: System.Environment
Environment.GetEnvironmentVariable(variable: string) : string
Environment.GetEnvironmentVariable(variable: string, target: EnvironmentVariableTarget) : string
val port : string
val nancy_host : obj
Multiple items
type Uri =
  new : uriString:string -> Uri + 5 overloads
  member AbsolutePath : string
  member AbsoluteUri : string
  member Authority : string
  member DnsSafeHost : string
  member Equals : comparand:obj -> bool
  member Fragment : string
  member GetComponents : components:UriComponents * format:UriFormat -> string
  member GetHashCode : unit -> int
  member GetLeftPart : part:UriPartial -> string
  ...

Full name: System.Uri

--------------------
Uri(uriString: string) : unit
Uri(uriString: string, uriKind: UriKind) : unit
Uri(baseUri: Uri, relativeUri: Uri) : unit
Uri(baseUri: Uri, relativeUri: string) : unit
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
Console.ReadLine() : string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore

The Procfile

The procfile tells heroku how to launch your application, in the case of self-hosted nancy the following works fine with the buildpack above.

web: mono appdir/bin/Debug/app.exe

Compiling and deploying

I had to enable build with xbuild in xamarin studio

xamarin studio

and edit the .fsproj to re-order the build order, so Program.fs sits last (or your file with the entrypoint)

re-order

git commit your changes and push to heroku git push heroku

This takes a while, as it needs to pull down the Mono environment from S3 (100mb), pull down Nuget and pull in Nugets dependencies im sure theres ways to speed this up with caching but have yet to try any of them out.

Projects/viewfiles git:(master) ▶ git push heroku
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 316 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)

-----> Fetching custom git buildpack... done
-----> MonoFramework app detected
-----> Copying mono to /tmp/build_1rpsx5dkzhkhv/vendor/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  103M  100  103M    0     0  1746k      0  0:01:00  0:01:00 --:--:-- 2273k
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  235k  100  235k    0     0   262k      0 --:--:-- --:--:-- --:--:--  262k
-----> Setting envvars
-----> Importing trusted root certificates
Mozilla Roots Importer - version 3.0.7.0
Download and import trusted root certificates from Mozilla's MXR.
Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.

Downloading from 'http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1'...
Importing certificates into user store...
140 new root certificates were added to your trust store.
Import process completed.

-----> Setting up nuget
Checking for updates from https://nuget.org/api/v2/.
Currently running NuGet.exe 2.2.1.
NuGet.exe is up to date.
-----> Installing dependencies with nuget
Successfully installed 'Nancy.Serialization.JsonNet 0.16.1'.
Successfully installed 'Nancy.Hosting.Self 0.16.1'.
Successfully installed 'Nancy.Authentication.Forms 0.16.1'.
Successfully installed 'Nancy.Viewengines.Razor 0.16.3'.
Successfully installed 'Nancy 0.16.1'.
Successfully installed 'System.Web.Razor.Unofficial 2.0.2'.
Successfully installed 'Newtonsoft.Json 4.5.11'.
-----> Compiling application
XBuild Engine Version 3.0.7.0
Mono, Version 3.0.7.0
Copyright (C) Marek Sieradzki 2005-2008, Novell 2008-2011.

Build started 03/31/2013 21:53:10.
__________________________________________________
Project "/tmp/build_1rpsx5dkzhkhv/viewfiles.sln" (default target(s)):
  Target ValidateSolutionConfiguration:
    Building solution configuration "Debug|x86".
  Target Build:
    Project "/tmp/build_1rpsx5dkzhkhv/viewfiles/viewfiles.fsproj" (default target(s)):
      Target PrepareForBuild:
    ... SNIP ...
    Done building project "/tmp/build_1rpsx5dkzhkhv/viewfiles/viewfiles.fsproj".
Done building project "/tmp/build_1rpsx5dkzhkhv/viewfiles.sln".

Build succeeded.
   0 Warning(s)
   0 Error(s)

Time Elapsed 00:00:04.1524600
-----> Discovering process types
       Procfile declares types -> web

-----> Compiled slug size: 110.5MB
-----> Launching... done, v25
       http://viewfiles.herokuapp.com deployed to Heroku

To [email protected]:viewfiles.git
   dae558e..35cd50f  master -> master

All in all, im pretty impressed so far with the performance on heroku, mind you i'm really not doing anything intensive and really have a lot more to learn with Nancy.

Hope this helps wayward travellers, attempting to accomplish similar!




comments powered by Disqus G+ Author linking