Scott Hanselman

Publishing an ASP.NET Core website to a cheap Linux VM host

September 01, 2016 Comment on this post [31] Posted in DotNetCore | Linux | Open Source
Sponsored By

A little Linux VM on Azure is like $13 a month. You can get little Linux machines all over for between $10-15 a month. On Linode they are about $10 a month so I figured it would be interesting to setup an ASP.NET Core website running on .NET Core. As you may know, .NET Core is free, open source, cross platform and runs basically everywhere.

Step 0 - Get a cheap host

I went to Linode (or anywhere) and got the cheapest Linux machine they offered. In this case it's an Ubuntu 14.04 LTS Profile, 64-bit, 4.6.5 Kernel.

I signed up for a tiny VM at Linode

Since I'm on Windows but I want to SSH into this Linux machine I'll need a SSH client. There's a bunch of options.

Step 0.5 - Setup a user that isn't root

It's always a good idea to avoid being root. After logging into the system as root, I made a new user and give them sudo (super user do):

adduser scott
usermod -aG sudo scott

Then I'll logout and go back in as scott.

Step 1 - Get .NET Core on your Linux Machine

Head over to http://dot.net to get .NET Core and follow the instructions. There's at least 8 Linuxes supported in 6 flavors so you should have no trouble. I followed the Ubuntu instructions.

To make sure it works after you've set it up, make a quick console app like this and run it.

mkdir testapp
cd testapp
dotnet new
dotnet restore
dotnet run

If it runs, then you've got .NET Core installed and you can move on to making a web app and exposing it to the internet.

NOTE: If "dotnet restore" fails with a segmentation fault, you may be running into this issue with some 64-bit Linux Kernels. Here's commands to fix it that worked for me on Ubuntu 14.04 when I hit this. The fix has been released as a NuGet now but it will be included with the next minor release of .NET Core, but if you ever need to manually update the CoreCLR you can.

Step 2 - Make an ASP.NET Core website

You can make an ASP.NET Core website that is very basic and very empty and that's OK. You can also get Yeoman and use the ASP.NET yeoman-based generators to get more choices. There is also the great ASP.NET MVC Boilerplate project for Visual Studio.

Or you can just start with:

dotnet new -t web

Today, this default site uses npm, gulp, and bower to manage JavaScript and CSS dependencies. In the future there will be options that don't require as much extra stuff but for now, in order to dotnet restore this site I'll need npm and what not so I'll do this to get node, npm, etc.

sudo apt-get install npm
sudo npm install gulp
sudo npm install bower

Now I can dotnet restore easily and run my web app to test. It will startup on localhost:5000 usually.

$ dotnet restore
$ dotnet run
scott@ubuntu:~/dotnettest$ dotnet run
Project dotnettest (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]
User profile is available. Using '/home/scott/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
Hosting environment: Production
Content root path: /home/scott/dotnettest
Now listening on: http://localhost:5000

Of course, having something startup on localhost:5000 doesn't help me as I'm over here at home so I can't test a local website like this. I want to expose this site (via a port) to the outside world. I want something like http://mysupermachine -> inside my machine -> localhost:5000.

Step 3 - Expose your web app to the outside.

I could tell Kestrel - that's the .NET Web Server - to expose itself to Port 80, although you usually want to have another process between you and the outside world.

You can do this a few ways. You can open open Program.cs with a editor like "pico" and add a .UseUrls() call to the WebHostBuilder like this.

var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://*:80")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();

Here the * binds to all the network adapters and it listens on Port 80. Putting http://0.0.0.0:80 also works.

You might have permission issues doing this and need to elevate the dotnet process and webserver which is also a problem so let's just keep it at a high internal port and reverse proxy the traffic with something like Nginx or Apache. We'll pull out the hard-coded port from the code and change the Program.cs to use a .json config file.

public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hosting.json", optional: true)
.Build();

var host = new WebHostBuilder()
.UseKestrel()
.UseConfiguration(config)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();

host.Run();
}

The hosting.json file is just this:

{
"server.urls": "http://localhost:5123"
}

We can also use "AddCommandLine(args) instead of "AddJsonFile()" and pass in --server.urls=http://*:5123 on the command line. It's up to you. You can also use the ASPNETCORE_URLS environment variable.

NOTE: I'm doing this work a folder under my home folder ~ or now. I'll later compile and "publish" this website to something like /var/dotnettest when I want it seen.

Step 4 - Setup a Reverse Proxy like Nginx

I'm following the detailed instructions at the ASP.NET Core Docs site called "Publish to a Linux Production Environment." (All the docs are on GitHub as well)

I'm going to bring in Nginx and start it.

sudo apt-get install nginx
sudo service nginx start

I'm going to change the default Nginx site to point to my (future) running ASP.NET Core web app. I'll open and change /etc/nginx/sites-available/default and make it look like this. Note the port number. Nginx is a LOT more complex than this and has a lot of nuance, so when you are ready to go into Super Official Production, be sure to explore what the perfect Nginx Config File looks like and change it to your needs.

server {
listen 80;
location / {
proxy_pass http://localhost:5123;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

Then we'll check it and reload the config.

sudo nginx -t 
sudo nginx -s reload

Step 5 - Keep your website running

The website isn't up and running on localhost:5123 yet (unless you've run it yourself and kept it running!) so we'll need an app or a monitor to run it and keep it running. There's an app called Supervisor that is good at that so I'll add it.

sudo apt-get install supervisor

Here is where you/we/I/errbody needs to get the paths and names right, so be aware. I'm over in ~/testapp or something. I need to publish my site into a final location so I'm going to run dotnet publish, then copy the reuslts into /var/dotnettest where it will live.

dotnet publish
publish: Published to /home/scott/dotnettest/bin/Debug/netcoreapp1.0/publish
sudo cp -a /home/scott/dotnettest/bin/Debug/netcoreapp1.0/publish /var/dotnettest

Now I'm going to make a file (again, I use pico because I'm not as awesome as emacs or vim) called /src/supervisor/conf.d/dotnettest.conf to start my app and keep it running:

[program:dotnettest]
command=/usr/bin/dotnet /var/dotnettest/dotnettest.dll --server.urls:http://*:5123
directory=/var/dotnettest/
autostart=true
autorestart=true
stderr_logfile=/var/log/dotnettest.err.log
stdout_logfile=/var/log/dotnettest.out.log
environment=ASPNETCORE_ENVIRONMENT=Production
user=www-data
stopsignal=INT

Now we start and stop Supervisor and watch/tail its logs to see our app startup!

sudo service supervisor stop
sudo service supervisor start
sudo tail -f /var/log/supervisor/supervisord.log
#and the application logs if you like
sudo tail -f /var/log/dotnettest.out.log

If all worked out (if it didn't, it'll be a name or a path so keep trying!) you'll see the supervisor log with dotnet starting up, running your app.

Hey it's dotnet on linux

Remember the relationships.

  • Dotnet - runs your website
  • Nginx or Apache - Listens on Port 80 and forwards HTTP calls to your website
  • Supervisor - Keeps your app running

Next, I might want to setup a continuous integration build, or SCP/SFTP to handle deployment of my app. That way I can develop locally and push up to my Linux machine.

Hey it's my ASP.NET Core app on Linux

Of course, there are a dozen other ways to publish an ASP.NET Core site, not to mention Docker. I'll post about Docker another time, but for now, I was able to get my ASP.NET Core website published to a cheap $10 host in less than an hour. You can use the same tools to manage a .NET Core site that you use to manage any site be it PHP, nodejs, Ruby, or whatever makes you happy.


Sponsor: Aspose makes programming APIs for working with files, like: DOC, XLS, PPT, PDF and countless more.  Developers can use their products to create, convert, modify, or manage files in almost any way.  Aspose is a good company and they offer solid products.  Check them out, and download a free evaluation.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
September 01, 2016 4:57
Very nice post, taught me a lot.

Just one note regarding pricing: if all you want is a decent Ubuntu server machine, DigitalOcean actually offers them starting at $5/month. A bit less RAM (0.5GB instead of 0.75GB) but the disk is SSD which is a tremendous advantage, especially on a shared rig. -> https://www.digitalocean.com/pricing/
September 01, 2016 5:19
Blazing fast on my Ubuntu 15.04 SSD VPS, thanks Scott.
September 01, 2016 5:54
Thanks Scott, can't wait to try it.

I'll just add a shout out to SSH in "git bash" - it's my go to SSH client, it's simple and works a treat, no weird config.
September 01, 2016 7:58
Actually we can make tutorial like that even shorter.

Step 1. Get Digital Ocean account
Step 2. Create Droplet with dokku
Step 3. clone .net core example repo that have Dockerfile in place
Step 4. Push it to your dokku.
...
profit!
September 01, 2016 9:24
Just a minor typo - '640 bit' should be '64 bit' ( Ubuntu 14.04 LTS Profile, 640bit, 4.6.5 Kernel.)
September 01, 2016 9:57
Great post Scott. This is something, which I was looking for and was making notes in the progress. Now, I have the note and could run it much faster than my trial and error. Thanks.
September 01, 2016 10:04
Another recommendation for an SSH client on Windows: MobaXTerm (http://mobaxterm.mobatek.net). It's a vary nice package of Cygwin+SSH+VNC+Xdmcp, plus a nice tabbed terminal and an X Server.
September 01, 2016 10:05
Awesome post Scott. I look forward to the docker version.
September 01, 2016 10:15
It's really very nice update on Asp.net core. You can get little Linux machines all over for between $10-15 a month. So it would be interesting to setup an ASP.NET Core website running on .NET Core. As you may know .NET Core is free, open source, cross-platform and runs basically everywhere. Here in this post, it is described in step wise so that anyone can understand. I had a little bit idea about Asp.net when I hosted my business through Myasp.net. Thanks a lot for sharing with us.
September 01, 2016 10:53
Thanks for sharing @Scott. Will sure try it out.
September 01, 2016 11:43
Thanks for linking to my NGINX blog post!
September 01, 2016 12:29
(know very little linux)

however

"sudo install npm"

failed for me with "install: missing destination file operand after 'npm'"

"sudo apt-get install npm"

worked however
tim
September 01, 2016 12:32
Great post as always, thanks Scott.
September 01, 2016 17:57
Nice one. I've written almost exactly the same tutorial on my blog recently here! I've actually now got both Core and MVC5 Mono apps running side by side on Linux in production - if anyone wondered if it was possible (details here). Core seems easier to set up than Mono / ASP.NET though that might be because I'd not heard of supervisor which does simplify the process.
September 01, 2016 19:24
Love this post! Also, I'm curious about the ssh client that is installed with Git for Windows. It is available in the "Git Bash" shell. It never seems to get mentioned anywhere, even though generally everyone has Git for Windows installed on their Windows system, and the mingw ssh it has (seemingly) works great.

Still, perhaps there is a weakness that version of ssh has that I should be aware of?
September 01, 2016 23:20
Nice but wondering how big the interest will be for .Net on linux. Linux people use a bunch of other tech.
September 02, 2016 0:33
Nice post! And for all the folks who want to use Visual Studio to develop and still run on linux, i just made a small tutorial video how to get from zero to cross-platform with asp.net core: https://youtu.be/2WJj57zBzFI

September 02, 2016 4:44
Thanks for the post, Scott. But have you see anyone who has come up with a decent solution to deployment? Manually copying files from my local machine to the server seems really outdated. Does .NET core have the equivalent of capistrano?
September 03, 2016 8:04
What about the Linux version of SQL Server, can we get the full how to please?
September 04, 2016 1:06
Very nice post. Haven't heard of SmarTTY, so I will check that out. Have you tried KiTTY?
September 04, 2016 19:51
Thanks for this guide! I had to make the following changes to get things working:

Instead of /src/supervisor/conf.d/dotnettest.conf the location I used was /etc/supervisor/conf.d/dotnettest.conf

And I also had to run the following to get nginx to actually read the file

sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
sudo nginx -s reload

September 05, 2016 0:32
There's also the Docker image approach which doesn't mean snowflake servers but a slightly steeper learning curve. Gitlab give you free Docker registry hosting, DigitalOcean costs $5 a month for a Docker host/machine. You just create the DockerFile:


FROM microsoft/dotnet
MAINTAINER Me

RUN mkdir /app
COPY ./src/MyProject.Web/bin/Debug/netcoreapp1.0/publish /app
WORKDIR /app

ENTRYPOINT ["dotnet", "/app/MyProject.Web.dll"]


Build it and push it (build.ps1):


dotnet restore
dotnet publish src/MyProject
docker login registry.gitlab.com/myusername/MyProject
docker build -t registry.gitlab.com/myusername/MyProject .
docker push registry.gitlab.com/myusername/MyProject


Run a NGinx Docker container with a conf file as a volume or copied onto the image, linked to the ASP.NET core site (which needs to listen using the format http://*:5000 or http://0.0.0.0:5000).

There's also this annoying bug with the current dotnet restore command:

https://github.com/dotnet/cli/issues/3681#issuecomment-244249366
September 05, 2016 0:34
Also worth mentioning that the MongoDB C# driver and Postgres C# Driver nuget packages are .NET Core compliant now, which gives you a cheap DB.
September 05, 2016 12:03
Spinning up a Droplet on Digital Ocean for $5 per month is a great way to get a dedicated VM that is fast and highly configurable, and easy with some of the best documentation available (I wish Microsoft could do documentation even half as well as Digital Ocean).

I suggest that, for anyone who uses SSH a lot, Token2Shell/MD is easily the best SSH client on WIndows and will make your SSH life very easy.

Token2Shell/MD is a Universal Windows App that installs on Windows Phone 10 and fully supports Continuum Dock. Since purchasing it for less than $10 on the Windows Store I haven't looked back. Full OpenSSH support, tabbed interface (can drag a tab to another monitor), SFTP right in the session, server profiles, etc.

Surprisingly, T2S/MD is significantly better than SecureCRT, which I previously used extensively for network devices.

Token2Shell/MD:
http://www.windowscentral.com/token2shellmd-shows-just-how-powerful-uwp-apps-can-be

I also recommend using TMUX to maintain your SSH sessions on your remote Linux server:
https://danielmiessler.com/study/tmux/

Thanks for the article, Scott. Super excited about .net core on Linux (and PowerShell coming, too). NGINX in front of Kestrel sounds like a great combination.
September 05, 2016 23:25
/src/supervisor/conf.d/dotnettest.conf should be /etc/supervisor/conf.d/dotnettest.conf
September 07, 2016 13:28
Hi everyone.

Scott, let me express my appreciations to you for a very nice and clear deployment topic.

What do you think about clustering of asp.net core web applications on linux ?


  • Should we cluster it on the same machine using nginx to spawn multi processes ?

  • Should we cluster it on the same machine using apache to spawn multi processes ?

  • Should we cluster it using a .net code to create multiple process when a master process start ? if so, should we need to take care of IPC ourselves in that case ?

  • Should we cluster it using IP Tables ?

  • Should we really need to cluster it ?

  • or am I confused ?


NOTE: As far as I red and considering I didn't misunderstand, spawning multiple process by using nginx said to open to hearth bleeding issues.
September 07, 2016 16:56
This is nice for your admittedly highly-technical audience. There is a ton of work for Microsoft to do to make this type of thing easy for non-highly-technical people. Seriously, look at the number of steps ... and if anything goes wrong, it's off to Google for hours of searching. I really think simplicity is often avoided so that programmers can say "look how clever I am." Let's change this mindset so that you don't need expertise in 12 languages/tools to get a website up (Linux command line, .NET, JavaScript, gulp, npm, ssh clients, docker, etc. etc. etc.).
September 09, 2016 18:05
Hi, Great post - Thanks.

Would this also work on a Raspberry Pi (Model B V2) running Raspbian?

thanks again.

Peter
September 15, 2016 15:50
Small type in supervisord config.

command=/usr/bin/dotnet /var/dotnettest/dotnettest.dll --server.urls:http://*:5123

should read

command=/usr/bin/dotnet /var/dotnettest/dotnettest.dll --server.urls=http://*:5123
September 21, 2016 7:24
Great info. Thanks!
September 27, 2016 7:28
Thanks Scott, i followed it like a book and it went exactly as you've mentioned.
Appreciate it!! THANK YOU

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.