Deploying .NET Core Apps to Amazon Linux 2 AMI

A common way for reliably hosting .NET Core Apps on Linux is to use supervisor to monitor the dotnet self-hosting processes behind an nginx reverse proxy which handles external HTTP requests to your website and proxies them to the dotnet process running your Web App on a local port. You'll need access to a Unix environment on your client Desktop, either using Linux, OSX or Installing Windows Subsystem for Linux (WSL).

Amazon Linux 2

Amazon Linux 2 is the next-generation Amazon Linux operating system that provides modern application environment with the latest enhancements from the Linux community and offers long-term support.

It is optimized for use in Amazon EC2 with a latest and tuned Linux kernel version. As a result, many customer workloads perform better on Amazon Linux 2.

We'll start by SSH'ing into your Amazon Linux server, e.g:

ssh -i ~/pem/<my>.pem ec2-user@ec2-<ip-address>.compute-1.amazonaws.com

Install .NET 6.0

Being based on RHEL you can use yum and the Cent OS 7 Install Instructions to install .NET Core on Amazon Linux 2:

If you just want a minimal ASP.NET Core runtime to run Web Apps you can just install:

sudo yum install aspnetcore-runtime-6.0

But if you'd also like to use dotnet tools like the x super utility you'll need to install the SDK:

sudo yum install dotnet-sdk-6.0

Then install dotnet tools you want which will install under the ec2-user home directory at ~/.dotnet/tools:

dotnet tool install --global x

You'll either need to exit & re login to configure the dotnet tool path or you can import it manually with:

. /etc/profile.d/dotnet-cli-tools-bin-path.sh

Where you should now be able be able to run dotnet tools, e.g:

x

Setup the deploy User Account

We'll then create a dedicated user account for hosting and running your .NET Core Apps to mitigate potential abuse. Create the deploy user account with a /home/deploy home directory and add them to the sudo group:

sudo useradd -m deploy

Create a password (optional):

sudo passwd deploy

For seamless deployments use visudo:

sudo visudo

To allow deploy to run supervisorctl without prompting for a password:

## Same thing without a password
# %wheel        ALL=(ALL)       NOPASSWD: ALL
%deploy ALL=(ALL:ALL) NOPASSWD: /usr/bin/supervisorctl

To give sudo commands access to installed dotnet tools add it to secure_path:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/ec2-user/.dotnet/tools

Tip

In vi type i to start editing a file and ESC to quit edit mode and :wq to save your changes before exiting

Now we'll create an ~/apps folder as the deploy user where your .NET Core Apps should be deployed to, e.g:

sudo su - deploy
mkdir ~/apps
exit

Setup supervisor

Install supervisor on Amazon Linux 2 by first enabling the EPEL repository:

sudo amazon-linux-extras install epel

Then use yum to install supervisor

sudo yum install supervisor

You'll also need to create a supervisord.service systemd script which you can install with:

sudo x mix supervisord.service

Which will write this supervisord.service gist to /usr/lib/systemd/system.

We'll also want to configure supervisor to look for our *.conf scripts in the conventional location:

echo 'files = /etc/supervisor/conf.d/*.conf' | sudo tee -a /etc/supervisord.conf

We can then enable & start the systemd supervisord.service with:

sudo systemctl enable supervisord.service
sudo systemctl start supervisord

You'll then need to create a separate config file for each app in /etc/supervisor/conf.d/.

We can use the same template below by replacing my-app with the name of your App:

/etc/supervisor/conf.d/app.my-app.conf

[program:app-my-app]
command=/usr/local/bin/dotnet /home/deploy/apps/my-app/MyApp.dll
directory=/home/deploy/apps/my-app
autostart=true
autorestart=true
stderr_logfile=/var/log/app-my-app.err.log
stdout_logfile=/var/log/app-my-app.out.log
environment=ASPNETCORE_ENVIRONMENT=Production,ASPNETCORE_URLS="http://*:5000/"
user=deploy
stopsignal=INT

We can use x mix to simplify this by downloading & renaming the supervisor configuration template above:

sudo x mix supervisor -name acme

You can further customize the template by adding any number of -replace term=with switches, e.g. you can replace the port with:

sudo x mix supervisor -name acme -replace 5000=5002

Then tell supervisor to register our App configuration:

sudo supervisorctl update

Setup nginx

Use yum to install nginx:

sudo yum install nginx

Start nginx

sudo systemctl start nginx.service

Create Virtual Host Configuration

You'll also need to create a separate config for each website on nginx in /etc/nginx/conf.d/. You can use the same template for each website but you'll need to change the server_name with the domain name you want to use for the App and use a different port number for each App:

/etc/nginx/conf.d/my-app.org.conf

server {
    listen       80;
    server_name my-app.org;

    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_cache_bypass $http_upgrade;
        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;
        proxy_buffering off;
        proxy_ignore_client_abort off;
        proxy_intercept_errors on;

        client_max_body_size 500m;
    }
}

Or use x mix to write the above template using your preferred port and TLD with:

sudo x mix nginx-yum -name acme -replace 5000=5002 -replace org=io

After this we can tell nginx to reload its configuration, as there's nothing listening to http://localhost:5002 yet nginx will return a 502 Bad Gateway response but will start working as soon as our deployed .NET Core Apps are up and running.

sudo nginx -s reload

Setting up SSH keys

We can now exit our remote Linux server and return to our local machine and prepare our deployment script. Before doing this we recommend setting up SSH and copying your SSH public key to your remote server which is both more secure and more convenient than using a password. You'll want to configure the ~/.ssh/authorized_keys for your deploy user account as well as ec2-user account for convenience.

A manual 'tool-free' solution if you're using WSL is to copy your SSH public key to the clipboard, e.g:

cat ~/.ssh/id_rsa.pub | clip.exe

Then as the deploy user paste the contents of id_rsa.pub to /home/deploy/.ssh/authorized_keys and ensure it has the correct restrictive permissions:

mkdir ~/.ssh
vi ~/.ssh/authorized_keys
# i + paste clipboard + <esc>:wq
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Create the deployment script

rsync is a beautiful utility that provides a fast, secure file transfer over SSH which you can use to sync the contents of folders to a remote site. There's only 2 commands you need to run to deploy a local .NET Core App remotely, rsync to sync the published .NET Core App files and supervisorctl to restart the supervisord process that runs and monitor the .NET Core App which you can add to a deploy.sh that you can run with WSL bash:

rsync -avz -e 'ssh' bin/Release/net5/publish/ deploy@ec2-<ip-address>.compute-1.amazonaws.com:/home/deploy/apps/acme
ssh deploy@ec2-<ip-address>.compute-1.amazonaws.com "sudo supervisorctl restart app-acme"

To automate the entire deployment down to a single command you can add an npm script to your project's package.json that creates a production client and server build of your App before running WSL's bash to run the deploy script. All Webpack Single Page App Templates already have a publish npm script, so you would just need to add a deploy script to run publish before running the above deploy.sh

{
    "publish": "dotnet publish -c Release",
    "deploy": "npm run publish && bash deploy.sh",
}

Now to deploy your App you can just run:

npm run deploy

Which deploys your published App to your remote Linux server instance using rsync to only copy the incremental parts of the App that's changed (typically completing in <1s) and ssh to run a remote command to restart the suprvisord process, starting the .NET Core App with the latest deployed version.

After you deploy your and restart your supervisor Service your .NET Core App should now be publicly available at your chosen domain, if you have issues you can view your App's error log for info, e.g:

tail -n 50 /var/log/app-[my-app].err.log

Setup Lets Encrypt

If you're configuring an Internet Website you'll also likely want to configure it to use SSL, the easiest & free way is to use letsencrypt.org which you can install with:

sudo yum install certbot python2-certbot-nginx

Then use certbot to automatically configure the domains you want to configure to use SSL, e.g:

sudo certbot -d acme.io -d www.acme.io

Now you're .NET Core creation should be accessible via https:// & the shiny new secure badge in the users Browsers URL.