You can host a .NET Core MVC app on Linux
Ya, rly.
I’ve missed the beginning of .NET Core, so I’ve just recently discovered that one can host a .NET Core MVC web-application on a Linux server. I always liked C#/.NET, but I didn’t like to be tied to IIS/Windows platform, so that looked very interesting to me.
The whole setup-from-scratch process takes about 15 minutes. I kid you not! And below I will show you it step-by-step (based on this instruction) - how to host a .NET Core MVC application with NGINX and Kestrel on a Linux server.
Publishing
So, we have:
- Clean, out-of-the-box, remote Linux server running some Linux
- Some .NET Core MVC application ready for deployment
Connect to your server via SSH:
$ ssh root@YOUR-SERVER-IP
Install .NET Core for Linux on your server and check if it works:
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 2.2.402
Commit: c7f2f96116
Runtime Environment:
OS Name: ubuntu
OS Version: 18.04
OS Platform: Linux
RID: ubuntu.18.04-x64
Base Path: /usr/share/dotnet/sdk/2.2.402/
Host (useful for support):
Version: 2.2.7
Commit: b1e29ae826
.NET Core SDKs installed:
2.2.402 [/usr/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.2.7 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.2.7 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.2.7 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
On your local machine publish your project by running the following from the project folder:
$ dotnet publish -c Release -o ../_deploy
Create a website folder on the server:
$ mkdir -p /var/www/YOUR-WEBSITE
And copy the contents of ../_deploy
from your machine to /var/www/YOUR-WEBSITE/
on the server.
Change the owner of your website’s directory so it would belong to NGINX’s user (www-data
):
$ chown -R www-data:www-data /var/www/
systemd service
Create a systemd
config for Kestrel
instance:
$ nano /etc/systemd/system/kestrel-YOUR-WEBSITE.service
Edit it like that:
[Unit]
Description=YOUR-WEBSITE
[Service]
WorkingDirectory=/var/www/YOUR-WEBSITE/
ExecStart=/usr/bin/dotnet /var/www/YOUR-WEBSITE/YOUR-WEBSITE.dll
Restart=always
RestartSec=10
SyslogIdentifier=dotnet-YOUR-WEBSITE
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
Enable and start it:
$ systemctl enable kestrel-YOUR-WEBSITE.service
$ systemctl start kestrel-YOUR-WEBSITE.service
You can check its status:
$ systemctl status kestrel-YOUR-WEBSITE.service
If everything is okay, then it will show something like that:
● kestrel-YOUR-WEBSITE.service - YOUR-WEBSITE
Loaded: loaded (/etc/systemd/system/kestrel-YOUR-WEBSITE.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2017-08-10 11:30:09 UTC; 1s ago
Main PID: 15628 (dotnet)
Tasks: 14
Memory: 25.4M
CPU: 1.380s
CGroup: /system.slice/kestrel-YOUR-WEBSITE.service
└─15628 /usr/bin/dotnet /var/www/YOUR-WEBSITE/YOUR-WEBSITE.dll
NGINX
Install NGINX:
$ apt-get install nginx
Now, there are 2 options how NGINX can send requests to Kestrel: via TCP or via Unix socket.
TCP
Your Program.cs
:
// ...
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://localhost:5000/")
.UseStartup<Startup>();
// ...
NGINX config (/etc/nginx/sites-available/default
):
# ...
server {
listen 80;
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;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# ...
Unix socket
This should be better in terms of performance as there is no TCP overhead. Even if there was no performance impact, on a local machine “talking” through Unix socket simply makes more sense than communicating over TCP.
Your Program.cs
:
// ...
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseLibuv()
.ConfigureKestrel(
(context, serverOptions) =>
{
// /var/www/YOUR-WEBSITE
string root = Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location
);
// if we are (behind NGINX) and on Linux, then can use sockets
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// path to socket has to be absolute
string socket = Path.Combine(root, "kestrel.sock");
serverOptions.ListenUnixSocket(socket);
}
else
{
serverOptions.Listen(IPAddress.Loopback, 5000);
}
}
)
.UseStartup<Startup>();
// ...
For that to work you need to add Transport.Libuv package, so you’ll have the following in your .csproj
:
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="2.2.0" />
NGINX config in this case:
# ...
location / {
proxy_pass http://unix:/var/www/YOUR-WEBSITE/kestrel.sock;
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;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ...
Save it and restart NGINX:
$ systemctl restart nginx.service
Now open your web-browser and go to http://YOUR-SERVER-IP/. It fucking works!
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks