Setting up .NET Core service/daemon on Debian Linux OS
How to run .NET Core application as daemon on Linux
I found a lot of articles out here on this subject, but non of them actually made the daemon work from the first time. There was always some catch I had to chase and found in some other article posted online.
In this article I will try to cover as much as possible how to setup .NET Core daemon working on a clean Debian 9 Linux machine. Let's start with the steps
I use Visual Studio 2017 and Windows as my development environment, so I will do the development and compilation on my Windows machine and just move binaries to a Linux machine where we are going to setup the service/daemon.
.NET Core project
Service implementation on Linux host has been updated for .NET Core 2.1. Please check this article
http://bit.ly/2JNuLnC for more details how to properly implement service for Linux environment with .NET Core 2.1 which handles SIGTERM for clean service stop
Unlike Windows, Linux does not have special concept and type of application as a service. Instead any console application can work as a service. Because of this, we are going to create a simple .NET Core console application.
It won't to any complex computation, we'll just save current date and time to a file on the filesystem every 5 seconds. Here is the sample code of Program.cs class
using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using System.Threading; namespace Service.Sample { class Program { static readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); static readonly String outFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "output"); static readonly String outFilePath = Path.Combine(outFolderPath, $"{DateTime.Now.ToString("yyyy-MM-dd")}.txt"); static void Main(string[] args) { AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; if(!Directory.Exists(outFolderPath)) { Directory.CreateDirectory(outFolderPath); } Console.WriteLine($"Output file: {outFilePath}"); File.AppendAllLines(outFilePath, new List<String>() { "------------------- SERVICE START ------------------- " }, Encoding.UTF8); while (!tokenSource.Token.IsCancellationRequested) { File.AppendAllLines(outFilePath, new List<String>() { DateTime.Now.ToString() }, Encoding.UTF8); Console.WriteLine(DateTime.Now); Thread.Sleep(5000); } File.AppendAllLines(outFilePath, new List<String>() { "------------------- SERVICE STOP ------------------- " }, Encoding.UTF8); } private static void CurrentDomain_ProcessExit(object sender, EventArgs e) { File.AppendAllLines(outFilePath, new List<String>() { "------------------- PROC EXIT SIGNAL ------------------- " }, Encoding.UTF8); tokenSource.Cancel(); } } }
At the end of the article I will explain why I put two places for writing service stop event.
From Visual Studio publish your project and copy output folder to your Linux machine to /etc/SampleService folder via FTP. If you do not have permissions to copy to this folder via FTP, upload published folder to your home and then just simply copy it from the terminal on Debian host.
Setting up .NET Core
I already explained how to install .NET Core in article How to setup .NET Core 2 on Debian or Ubuntu Linux distro the easy way but I will make some small adjustments to the installation to make it more usable for running services, so I will shortly explain here how to setup .NET Core support.
Execute the following commands one by one in your terminal
mkdir $HOME/dotnet cd $HOME/dotnet wget https://download.microsoft.com/download/D/7/2/D725E47F-A4F1-4285-8935-A91AE2FCC06A/dotnet-sdk-2.0.3-linux-x64.tar.gz tar zxf dotnet-sdk-2.0.3-linux-x64.tar.gz sudo cp -r ./dotnet /bin sudo apt-get install -y libunwind-dev export PATH=$PATH:/bin/dotnet
After this you will have .NET Core support installed on your machine. To double check execute the following command in terminal
/bin/dotnet/dotnet --version
You should get 2.0.3 as output
Service/Daemon setup
First thing is to check that we have systemd package installed
sudo apt-get install -y systemd
Second thing is to setup the user we are going to run our service under. I named it dotnetuser
useradd -m dotnetuser -p dotnetpass
Finally we can go and setup the service configuration. Navigate to systemd configuration folder
cd /etc/systemd/system
Open nano, paste the following content and save as dotnet-sample-service.service
[Unit] Description=Dotnet Core Demo service [Service] ExecStart=/bin/dotnet/dotnet Service.Sample.dll WorkingDirectory=/etc/SampleService/ User=dotnetuser Group=dotnetuser Restart=on-failure SyslogIdentifier=dotnet-sample-service PrivateTmp=true [Install] WantedBy=multi-user.target
More about .service configuration files and options you can include in your service configuration files you can find at https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files so I'll not go deeper into this.
Now we need to tell systemd to reload service configurations and create metalink for our service
systemctl daemon-reload systemctl enable dotnet-sample-service.service
Our service is ready now for start
systemctl start dotnet-sample-service.service systemctl status dotnet-sample-service.service
Since our code will try to write file to /etc/SampleService/output folder, our service start might fail because of insufcient permissions to perform writing to this location. It's not a big deal, just go create the folder and change the cmod
mkdir /etc/SampleService/output chmod 777 mkdir /etc/SampleService/output
Another way to check if your service is up and running is to check it via top or my favorite htop package. The first one comes pre-installed but if you want to use htop you will have to install it
sudo apt-get install htop
To stop the service, just call systemctl stop.
systemctl stop dotnet-sample-service.service systemctl status dotnet-sample-service.service
Tracing service log
As in any .NET Core application, you can setup logging, but you can also just write everything to console. It is much easier for debugging and guess what, you can get all these values from the service run.
All these values are traced and you can easily fetch them
journalctl --unit dotnet-sample-service --follow
Handling service stop in your code
Unlike windows service, .NET Core console is not aware of service stop request from the systemctl command, there for, if you check the output file of this sample service upon stop, you will not find the comment line which comes after while loop.
This is something that is missing in .NET Core so far, but considering the speed of framework evolving, I am sure it will be introduced in a future releases
References
- https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files
- https://www.microsoft.com/net/download/linux
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article