2016-07-11

You may have seen the release of .NET Core and ASP.NET Core 1.0 was announced at DevNation recently.

During the KeyNote presentation Scott Hanselman demonstrated deploying a .NET Core application to a Red Hat Linux server using Octopus Deploy (Scott takes the stage at 48:15 and demonstrates Octopus Deploy integration at 1:02:00).

We were honored to get a mention during Scott's presentation. And we're excited about the possibilities for .NET Core.

Scott understandably skipped the gory details. For those interested, this post will dive deeper into what a real-world-ish deployment of an ASP.NET Core application to a Linux server might look like.

Disclaimer: IANALG (I Am Not A Linux Guy). But I think that's the point. Most .NET developers are (at least currently) most familiar with Windows. For many of us, Linux is a strange (and if we're honest, scary) new world. Come on, let's hold hands...

Best Before

The problem with writing a technical procedure post is that it's often obsolete by the time you finish writing it (a problem very apparent when following posts regarding .NET Core).

Wherever possible I have referenced official documentation, which is far more likely to be maintained.

Ingredients

Obviously we will require an Octopus Deploy Server. If you don't have one handy, download a trial instance or spin one up from the Azure Marketplace.

A Linux server to deploy to. Following Mr Hanselman's lead, we will use a server running Red Hat Enterprise Linux 7.2. You can easily create a RHEL VM in Azure.

An ASP.NET Core application to deploy. We will use a demonstration project created for this purpose: https://github.com/MJRichardson/aspnetcoredemo

It is trivial, but contains two relevant features:

It uses configuration settings from appsettings.json (into which we will substitute Octopus variables)

It contains some configuration files under the \conf directory which we will use to configure our Linux server.

Create a Package

If you want to skip creating the package, you can download the zip file from the Releases page on the GitHub repository.

If you haven't already, clone the sample application repo:

Move to the directory you cloned the project into. The following commands will be run from there.

Note: We have a saying at Octopus HQ: "Friends don't let friends right-click-publish". In a .NET Core world we may have to update it to: "Friends don't let friends dotnet publish". Creating your package and pushing it to Octopus should be performed by your Build Server. Plugins are available for most popular Build Servers (e.g. Team City, Jenkins, TFS).

See here for our official documentation on publishing ASP.NET Core applications to Octopus.

Restore the NuGet packages:

Publish the application to a directory:

The contents of the \published directory will be the contents of our package. You can use your favorite archiving tool (I recommend 7-Zip) to archive the contents of \published (not the directory) into a file named aspnetcoredemo.1.0.0.zip.

Now upload the package to Octopus Deploy.



You should now see your published package:



Create an SSH Target in Octopus

Octopus Requirements

There are a couple of requirements the Red Hat Linux server must satisfy in order to be added as an SSH Target in Octopus:

Mono

Mono must be installed. The most up-to-date instructions can be found in the Mono docs. For a RHEL server, follow the 'CentOS and derivatives' section.

In a root shell, execute:

SSH Key

Octopus will authenticate with the RHEL server using an SSH Key Pair. A guide for creating a key-pair can be found here.

In your Linux shell, generate an SSH key-pair:

Add the public key to the authorized keys:

Ensure correct ownership of the SSH directories:

Other Requirements

.NET Core

Obviously we need .NET Core. Instructions for installing .NET Core on RHEL can be found here.
For full disclosure, this author followed the CentOS instructions rather than using subscription-manager.

NGINX

For our little demonstration application, there is no reason we couldn't have Kestrel directly serving requests. But the consensus seems to be that best-practice is to use a production-grade web server as a reverse-proxy in front of your ASP.NET Core application. On Windows this would be IIS.

We'll use NGINX. Following the instructions found at https://www.nginx.com/resources/wiki/start/topics/tutorials/install/#
I create a file /etc/yum.repos.d/nginx.repo, and edited it to contain:

I also modified /etc/nginx/nginx.conf to include the line:

and we should also create that directory:

This will be important when we deploy our application.

So the http section appears as:

Supervisor

An ASP.NET Core application is run by executing the dotnet utility. Executing it directly via the terminal is fine when you are testing locally, but for deploying to a server we need:

To be able to start and stop the service

For the service to start automatically on server restarts

So we'll use Supervisor.

Following the install instructions:

And we will create a Supervisor configuration file using default settings, as per here:

Now we will create a directory to hold our application-specific supervisor config:

And edit /etc/supervisor/supervisord.conf and add at the end:

Note: Installing Supervisor using easy-install does not appear to register Supervisor with SYSTEMD. This seems like something you would certainly want to do. A nice walk-through of the complete supervisor setup can be found here.

Security

By default your RHEL server will likely be rather locked down (as it should be). Since we are going to be using it as a web server, we need to loosen the chains.

I can't state strongly enough that I am not qualified to provide Linux security advice. Please consult your local sysadmin. And apologize in advance.

We need to open port 80:

By default, SELinux was preventing NGINX from proxying HTTP requests to Kestrel.
Information regarding this can be found here.

Running the following should allow connections to be made:

Create an Environment

Since all Targets need to belong to an Environment, let's create an Environment first.



Create the SSH KeyPair Account

Octopus documentation.

The private key can be obtained by:

You will need to save the text into a file, which will then be supplied for the 'Private key' field as shown below.

Create the SSH Target

Once you have created the target you can run a health-check to ensure connectivity.

Create a Project in Octopus

Now we will create a project to deploy our ASP.NET Core Demo application.

Add Deploy a Package Step

Add a Deploy a Package step to your Project.

It will reference the package we previously uploaded.

We will enable two features for this step:

JSON Configuration Variables

We will use the JSON Configuration Variables feature to transform our appsettings.json file.

In this case we are simply changing the rendered message, but this demonstrates how Octopus can transform hierarchical variables in your JSON configuration files.

Substitute Variables in Files

We will also use the Substitute Variables in Files feature to supply variables to our NGINX and Supervisor configuration files.

If you view these files (located in src\aspnetcoredemo\deployment), you will see they contain placeholders for Octopus variables. For example, the nginx.conf file contains the #{IPAddress} variable:

Configure NGINX

Next we will add a Run a Script Step to move our NGINX configuration file to /etc/nginx/sites-enabled and tell NGINX to reload.

The script source is:

The first line gets the path to the extracted package.

Being able to drop files into the sites-enabled directory saves us from having to modify the existing nginx.conf.

Apparently a common approach is to have both a sites-available and sites-enabled directories. The actual configuration files are deployed to sites-available and symlinks are added to sites-enabled. It is then sites-enabled which is included in nginx.conf. I'll leave this for homework.

Run Supervisor

Now we will add another Run a Script Step to move our Supervisor configuration file to /etc/supervisor/conf.d/aspnetcoredemo.conf and tell Supervisor to reload.

The script source is:

Variables

And finally we need to add the variables which will be substituted into our configuration files.

Deploy

At this point your deployment process should appear similar to:

What are you waiting for? Create a Release, and Deploy!

If all has gone well, you have just deployed an ASP.NET Core application to a Red Hat Enterprise Linux server.

Note to self: Add balloons.png to sample app home-page ;)

XPlat

Although it's beyond the scope of this post, it's worth mentioning that with very little effort, we can take that same package and deploy it to also both a Windows 2012 R2 Server and an Azure Web App.

The Future

.NET Core provides a great opportunity for us to improve our story for deploying to Linux targets.

Sans Mono

The fact that you currently have to install the Mono framework on your Linux server in order for it to be an Octopus Deployment Target is awkward in many cases. We are moving towards making our deployment tasks execute using .NET Core, removing the Mono dependency completely.

Sans Script

A major focus for us is to provide the capability to deploy what you want, to where you want, without having to script it yourself.
Octopus has historically been targeted at .NET, and .NET has historically been targeted at Windows. .NET Core has breached the Linux wall. We should be able to march through and implement some more Linux-focussed deployment steps.

Feedback

If you are using Octopus (or even thinking of using it) to deploy .NET Core applications to Linux, we would love to hear from you.

Please tell us what works, what doesn't, and how we can make your deployments easier and more reliable.

Happy (cross-platform) deployments!

Show more