Deploying and hosting ASP.NET Core applications on Ubuntu Linux

If you’ve read my other blog posts or seen some of my projects on my GitHub profile, you’ll see that I’m a real Linux fan and although I develop in both PHP and C#, C# based web applications are commonly hosted on the Microsoft Windows platform (although you could use Mono) so for this reason, it has previously made my first choice for developing web applications choosing a technology stack that enabled me to control whether I wanted to host on Windows, Linux, Mac or BSD.

Microsoft not so long ago released .NET Core – .NET Core is open-source and supports cross-platform operating systems.

I wanted to make a blog post about how to configure and host a simple (but functional) C#/ASP.NET MVC application on Ubuntu Linux, ror this tutorial I created a very simple, working but fictional URL shortner service. I’ve built this using ASP.NET Core 5 and Entity Framework 6. Source code available here.

In this tutorial we will use the latest stable version of Nginx (from the Nginx PPA) of which will be configured to reverse proxy requests to our application server which will be running Kestrel. systemd will be used for starting our application server and monitoring the process.

This tutorial assumes that you already have a user account setup and has been granted sudo rights. My virtual machine is configured with 2GB of RAM.

Download the source code

First up, we’ll download (clone) the C# source code to our user home directory from my public GitHub repository. Using the Git CLI tool we can download as follows:

mkdir ~/source
cd ~/source
git clone https://github.com/allebb/lk2.git

That’s great, we now have the public repository downloaded to our user home directory under a folder name ‘source’.

Further on in this tutorial we will “publish” our application from this directory to our web hosting directory which will then be used to serve the application.

Setup Nginx

We will now add the official Nginx package repository to our server as this will enable us to then download the required packages from the Nginx package repository.

So using vim, we will create a new file under /etc/apt/sources.list.d/ named nginx.list like so:

sudo vi /etc/apt/sources.list.d/nginx.list

The file content should be as follows:

deb http://nginx.org/packages/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/ubuntu/ xenial nginx

Once done, save the file and we now need to update our package sources like so:

sudo apt-get update

If you get an error when running the update command (as per my screenshot below) you will need to import the key first (eg. sudo apt-key adv –keyserver keyserver.ubuntu.com –recv-keys ABF5BD827BD9BF62
) and then re-run the apt-get update command (the key can be found in the error message!!) like so:

Great! We can now get on with installing Nginx:

sudo apt-get install nginx

We can now start Nginx by running:

sudo systemctl start nginx

To test it’s all working as expected, enter the IP or FQDN into a browser to test that you are presented with the Nginx “welcome” page like so:

Install Microsoft .NET Core

We can install .NET Core on our Ubuntu server by running the following commands (taken from the official Microsoft page):

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.1

We can now test that we have it installed by running:

dotnet --help

All going well, you should see the following output:

Lets move…

Setup a hosting location for your web application

We need somewhere on our server to deploy to and store our production published web application this could be in a dedicated user’s home directory (and make the service run as that user) but for this example we will just store is under /var/webapps/. Let’s create that directory now…

sudo mkdir /var/webapps

Great!

Deploy (publish) our web application

Although we’ve downloaded the source code for our application, we need to “publish” it, Publishing a .NET Core application essentially bundles all the required run-time libraries (in the form of DLL’s) and copies across our appsettings.json file as well as any public assets.

Our source code contains our application code but also makes references to external libraries, I’ve used NuGet to manage these dependencies.

We’ll now change into our application’s root directory which will then able us to pull in/download all the required NuGet packages, lets do that now…

cd ~/source/lk2
dotnet restore

This will take a few minutes to complete but once done you should see the following output:

Now that’s completed we can now “publish” our application, this effectively compiles our code and bundles all the required dependencies to a specific directory:

sudo dotnet publish -c Release -o /var/webapps/lk2

The above command states that we want to build our project for “Production” and we have specified the output path as /var/webapps/lk2.

You should see the following output:

We then need to set the permissions so that the user that our service is running under (by default this will be www-data in this example) can have the required access rights, we do this by running:

sudo chown -R www-data:www-data /var/webapps/lk2

Lets see if we can now run the server in this directory as the www-data user…

sudo -u www-data dotnet /var/webapps/lk2/lk2-demo.dll

I have enabled “auto migrations” at startup – Although not recommended for production environments, I wanted to ensure that the user following this tutorial can get up and running as quickly as possible and after all, this tutorial is really to prove the ability to host ASP.NET Core web applications on Linux…

Running the above command you will see the following output, as you can see our Entity Framework database migrations have been run automatically for us (and we now have a SQLite database that has been created under /var/webapps/lk2).

You could also use Microsoft SQL Server if you wanted, you’d simply need to edit the appsettings.json file found in /var/webapps/lk2 and change the driver to mssql and set the corresponding connection string. You could also check out my recent blog post about installing and use Microsoft SQL Server on Linux.

Lets now kill the local Kestrel process by pressing CTRL+C on our keyboard and we’ll move on…

Create our systemd file

So in order for us to control our web application service (and have it start automatically) we will use vim to create a daemon (service) init script, using Vim we can run:

sudo vi /etc/systemd/system/lk2.service

Now copy the following content into it (making any path and environment variable changes you require for you application or environment):

[Unit]
 Description=LK2 .NET Core App

 [Service]
 WorkingDirectory=/var/webapps/lk2
 ExecStart=/usr/bin/dotnet /var/webapps/lk2/lk2-demo.dll
 Restart=always
 RestartSec=10
 SyslogIdentifier=dotnet-lk2
 User=www-data
 Environment=ASPNETCORE_ENVIRONMENT=Production 

 [Install]
 WantedBy=multi-user.target

Great, now save the file!

We can now “enable” the service (making it auto-start at boot time) by running:

sudo systemctl enable lk2.service

Now we can manually start it:

sudo systemctl start lk2.service

If you wanted to check the service status you can run:

sudo systemctl status lk2.service

Checking the status of the service at this point should output the following:

Good stuff – lets’ move on…

Configuring Nginx

I appreciate that we’re jumping around a bit here but now that you have the daemon configured and your application deployed and running we will now head back to Nginx and configure it to reverse-proxy requests to Kestral (running locally on port 5000)…

In this example I will show you the simplest way to reverse-proxy traffic to your application – In another blog post I will elaborate on this further with Nginx virtualhost configurations in addition to securing your application with HTTPS and setting some security headers too but for now we’ll simply prove this works…

Lets open the Nginx configuration:

sudo vi /etc/nginx/conf.d/default.conf

Now then replace the contents of the file with:

server {
 listen 80;
 location / {
 proxy_pass http://localhost:5000;
 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;
 }
}

Next we will create a proxy configuration file so that we can re-use in other virtual host configurations in future, create the new file like so:

sudo vi /etc/nginx/proxy.conf

Populate it with the following content:

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10m; 
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;

In order for these settings to be picked up by Nginx we must now include them into our main Nginx configuration file, to do this lets edit the file:

sudo vi /etc/nginx/nginx.conf

So, below the ‘http {‘ line, add ‘include /etc/nginx/proxy.conf;‘ as shown here:

Save the changes and quickly test that we’re still all good:-

sudo nginx -t

Now save the file and then we’ll reload the Nginx configuration like so (alternatively you could use sudo nginx -s reload which essentially does the same thing!):

sudo systemctl reload nginx.service

If all went well you should be able to access the web application by entering your server’s IP or FQDN in a browser and you should then see your application’s homepage like so:

Deploying code changes

In future, when you have made source changes you would need to commit those to Git, then change into the source code directory (cd ~/source/lk2), pull the latest changes (git pull) and the re-run the publish command. Once the latest source has been built you would then need to restart the application service by running (sudo systemctl reload lk2.service).

We’re done!

I hope you found this tutorial useful – If there is anything specific you want me to write about/elaborate on this post please drop me an email 🙂

Share