Getting a C# Server Working with Dokku and Vagrant

Developing a self-hosted web server in C# to run on Linux is like solo climbing a mountain: not a lot of people will be accompanying you; and the route will be unmarked, unmaintained, and often dangerous. In the digital world, there are two groups of programmers: people who use Node.js, Java, Ruby, etc. hosted on Linux; and people who use C# hosted on Windows. Building a C# server to run on Linux isn’t usually done because there is a perception that Mono is buggy. Most of those arguments are from experiences over 5 years ago, and Mono has come a long way. Of course, there is always room for improvement.

In particular, if you are a developer that wants to create services without focusing on all the crap associated with deployment, then Dokku is the way to go. I definitely prefer a command-line deployment rather than working with IDEs and browsers. Dokku, modeled after Heroku, is a PAAS model whereby the deployment of the server is simply a “git push.”  Unfortunately, the problem with Dokku and C# programming is that the easy “git push” deployment is offset by the difficulty in setting up the environment.

With that in mind, my goal was:

Prerequisites

Dokku requires you to work with SSH (both client and server), so you need to understand SSH very well. It is likely you have to understand how to configure your DHCP router, as I could not get Vagrant to send the DHCP router the name of the virtual machine. Also, if you aren’t familiar with Heroku, you might like to give that a spin with the Java example in order to understand some of the concepts.

Method

  1. Create a vanilla Ubuntu virtual machine, 20 GB disk space, 2048 memory, user “ubuntu”.
    1. Download the latest Ubuntu release from http://www.ubuntu.com.
    2. Start Virtualbox, and create a new virtual machine with a unique virtual_box_name running Ubuntu.
    3. Note: use the 64-bit “desktop” version of the OS. Make sure you have enough disk space for the large ISO file. You will also need at least 10GB of free disk space for the virtual machine itself.
    4. Install Ubuntu. Add user, e.g., “ubuntu”. Note: The Dokku installation scripts create user “dokku”. In order to login, you will want another account other than dokku.
    5. Restart the virtual machine.
    6. Install the VirtualBox Guest Additions.
    7. Restart the virtual machine.
    8. Start a terminal command-line interface in the virtual machine.
    9. sudo apt-get install openssh-server
    10. sudo visudo
      1. Add user ALL=(ALL) NOPASSWD: ALL to the file. This updates user with superuser rights so you don’t have to give a password for every sudo command.
    11. Shutdown the virtual machine.
  2. Create a Vagrant package of the vanilla Ubuntu virtual machine.
    vagrant box remove my-box
    vagrant init my-box
    vagrant package --base virtual_box_name
    vagrant box add my-box package.box
  3. Start the Vagrant virtual machine.
    1. Get a copy of my ‘kukku’ vagrant setup files at https://ken_domino@bitbucket.org/ken_domino/kukku.git
    2. cd kukku
    3. In the Vagrantfile, note the lines for setting up the user id and password for signing into the virtual machine, etc. Make sure they are in agreement with the names you gave in setting up the virtual machine.
      # -*- mode: ruby -*-
      # vi: set ft=ruby :
      # encoding: UTF-8
      
      BOX_NAME = ENV["BOX_NAME"] || "my-box"
      DOKKU_DOMAIN = ENV["DOKKU_DOMAIN"] || "kukku.home"
      # DOKKU_IP = ENV["DOKKU_IP"] || "10.0.0.2"
      PUBLIC_KEY_PATH = "#{Dir.home}/.ssh/id_rsa.pub"
      
      Vagrant.configure(2) do |config|
      
        # Set the name of the vagrant image file.
        config.vm.box = BOX_NAME
      
        # Display the Virtualbox GUI.
        config.vm.provider "virtualbox" do |v|
          v.gui = true
        end
      
        # Declare the name of the VM folder.
        config.vm.provider "virtualbox" do |v|
          v.name = "kukku-fun"
        end
      
        # Set the network to use bridge adapter, with a static IP, and known MAC.
        # My router does not handle the name of the guest correctly, so this bypasses
        # the problem by fixing it all. Note, in the router, I create an entry for
        # "whack" because the name isn't being broadcasted from the guest to the router.
        config.vm.network "public_network", ip: "192.168.1.33", :mac => "080027EA2F24"
        
        # Tell Vagrant to not muck around with the .ssh/authorized_hosts file.
        # It should not exist, or if it does, it should be empty. This is make sure
        # Dokku ssh works.
        config.ssh.insert_key = false
        config.ssh.private_key_path = File.expand_path("../.ssh/vagrant", __FILE__)
      
        # Define user id and password for login.
        config.ssh.username = "ubuntu"
        config.ssh.password = "ubuntu"
        
        # Provision the box with Dokku.
        config.vm.provision "shell", inline: >>-SHELL
      
          wget https://raw.githubusercontent.com/dokku/dokku/v0.4.14/bootstrap.sh
          sudo DOKKU_TAG=v0.4.14 bash bootstrap.sh
      
        SHELL
      end
      
      
    4. vagrant up
  4. Set up Dokku, and run the C# server.
    1. In the virtual machine…open a browser, and enter the address “localhost/”. It should be the Dokku set up screen.
    2. In the setup screen, enter the contents of your ~/.ssh/id_rsa.pub file, and set the name of the box to ‘whack’ or what ever machine you set for the machine. Click the button to set up Dokku.
    3. In the host machine…
      1. Verify that you see the help screen for Dokku via ‘ssh dokku@whack’. Otherwise, the ~dokku/.ssh/authorized_keys file on the virtual machine has the wrong entry. Edit that file, or type cat ~/.ssh/id_rsa.pub | ssh dokku@whack "sudo sshcommand acl-add dokku foobar"
      2. ssh dokku@whack apps:create fun
      3. ssh dokku@whack config:set fun BUILDPACK_URL=https://github.com/AdamBurgess/heroku-buildpack-mono
        1. Note: Adam Burgess’ C# buildpack is the only buildpack that seems to work. There are at least 4 others which I tried and don’t work. This buildpack uses xbuild for the build process.
      4. Get a copy of my “Hello World!” https://ken_domino@bitbucket.org/ken_domino/hn.git
        1. Note, you can substitute your program, but it should be an http server, and it must access the environmental variable PORT for the port to open. Dokku monitors that port to verify that the program is operating.
      5. cd hn
      6. git remote add foo dokku@whack:fun
      7. git push foo master
      8. Note: you should see output like the following.
        Ken@Win8-GAZ77X /cygdrive/c/Users/Ken/Documents/hn
        $ git push foo master
        Counting objects: 32, done.
        Delta compression using up to 4 threads.
        Compressing objects: 100% (25/25), done.
        Writing objects: 100% (32/32), 916.64 KiB | 0 bytes/s, done.
        Total 32 (delta 3), reused 0 (delta 0)
        -----> Cleaning up...
        -----> Building fun from herokuish...
        -----> Adding BUILD_ENV to build environment...
        -----> Fetching custom buildpack
        -----> .NET app detected
        -----> Installing mono version 636c0c95cc78-minimal
        -----> Downloading https://github.com/AdamBurgess/heroku-buildpack-mono/releases/download/636c0c95cc78/mono-minimal.tar.xz
        -----> Installing SSL certificates
        -----> Installing NuGet packages
               Installing 'Nancy.Hosting.Self 1.4.1'.
               Installing 'Nancy 1.4.3'.
               Successfully installed 'Nancy.Hosting.Self 1.4.1'.
               Successfully installed 'Nancy 1.4.3'.
        -----> Compiling .NET application
               XBuild Engine Version 14.0
               Mono, Version 4.3.3.0
               Copyright (C) 2005-2013 Various Mono authors
        
               Build started 03/04/2016 19:07:53.
               __________________________________________________
               Project "/tmp/build/hn.sln" (default target(s)):
               Target ValidateSolutionConfiguration:
               Building solution configuration "Release|Any CPU".
               Target Build:
               Project "/tmp/build/hn.csproj" (default target(s)):
               Target PrepareForBuild:
               Configuration: Release Platform: AnyCPU
               Created directory "obj/Release/"
               Target CopyFilesMarkedCopyLocal:
               Copying file from '/tmp/build/packages/Nancy.1.4.3/lib/net40/Nancy.dll' to '/tmp/build/Nancy.dll'
               Copying file from '/tmp/build/packages/Nancy.Hosting.Self.1.4.1/lib/net40/Nancy.Hosting.Self.dll' to '/tmp/build/Nancy.Hosting.Self.dll'
               Target GenerateSatelliteAssemblies:
               No input files were specified for target GenerateSatelliteAssemblies, skipping.
               Target CoreCompile:
               Tool /tmp/build/mono/lib/mono/4.5/mcs.exe execution started with arguments: /noconfig /debug:pdbonly /optimize+ /out:obj/Release/hn.exe Program.cs Properties/AssemblyInfo.cs obj/Release/.NETFramework,Version=v4.5.AssemblyAttribute.cs /target:exe /define:TRACE /nostdlib /platform:AnyCPU /reference:packages/Nancy.1.4.3/lib/net40/Nancy.dll /reference:packages/Nancy.Hosting.Self.1.4.1/lib/net40/Nancy.Hosting.Self.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Xml.Linq.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Data.DataSetExtensions.dll /reference:/tmp/build/mono/lib/mono/4.5-api/Microsoft.CSharp.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Data.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Net.Http.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Xml.dll /reference:/tmp/build/mono/lib/mono/4.5-api/System.Core.dll /reference:/tmp/build/mono/lib/mono/4.5-api/mscorlib.dll /warn:4
               Program.cs(41,17): warning CS0162: Unreachable code detected
               Target _CopyAppConfigFile:
               Copying file from '/tmp/build/App.config' to '/tmp/build/hn.exe.config'
               Target DeployOutputFiles:
               Copying file from '/tmp/build/obj/Release/hn.exe.mdb' to '/tmp/build/hn.exe.mdb'
               Copying file from '/tmp/build/obj/Release/hn.exe' to '/tmp/build/hn.exe
               Done building project "/tmp/build/hn.csproj".
               Done building project "/tmp/build/hn.sln".
        
               Build succeeded.
        
               Warnings:
        
               /tmp/build/hn.sln (default targets) ->
               (Build target) ->
               /tmp/build/hn.csproj (default targets) ->
               /tmp/build/mono/lib/mono/xbuild/14.0/bin/Microsoft.CSharp.targets (CoreCompile target) ->
        
               Program.cs(41,17): warning CS0162: Unreachable code detected
        
               1 Warning(s)
               0 Error(s)
        
               Time Elapsed 00:00:00.4912070
        -----> Discovering process types
               Procfile declares types -> web
        -----> Releasing fun (dokku/fun:latest)...
        -----> Deploying fun (dokku/fun:latest)...
        -----> DOKKU_SCALE file not found in app image. Generating one based on Procfile...
        -----> New DOKKU_SCALE file generated
        =====> web=1
        -----> Running pre-flight checks
               For more efficient zero downtime deployments, create a file CHECKS.
               See http://dokku.viewdocs.io/dokku/checks-examples.md for examples
               CHECKS file not found in container: Running simple container check...
        -----> Waiting for 10 seconds ...
        -----> Default container check successful!
        =====> fun container output:
               http://localhost:5000/
        =====> end fun container output
        -----> Running post-deploy
        =====> renaming container (d6dc5cffa4c9) nauseous_yonath to fun.web.1
        -----> Setting config vars
               DOKKU_NGINX_PORT: 80
        -----> Creating http nginx.conf
        -----> Running nginx-pre-reload
               Reloading nginx
        -----> Setting config vars
               DOKKU_APP_RESTORE: 1
        =====> Application deployed:
               http://dokku:32769 (container)
               http://dokku:80 (nginx)
        
        To dokku@dokku:fun
         * [new branch]      master -> master
        
        Ken@Win8-GAZ77X /cygdrive/c/Users/Ken/Documents/hn
        
    4. In a browser, type ‘whack/’. You should see “Hello world!”.

    Further Information

     

    An Important Side Note

    Part of the motivation for me was to re-examine what is available in website hosting. Currently, I use GoDaddy.com . After years of enduring GoDaddy ads that employ sophomoric humor, themes of misogyny, homophobia, and animal cruelty take on another light when juxtaposed by the actions of the son of Republican GoDaddy founder. It’s particularly important for me because I started a non-profit called “Hikers and Climbers Against Domestic Violence,” which tries to bring attention to the violence some women endure. While GoDaddy does offer good services at a low price, perceptions matter, and I’m shopping around to see what are my best options.