Deploying Web Apps

As all Web Apps use the same pre-compiled /web binary, deployment is greatly simplified as it only needs to be concerned with deploying static files and starting web/app.dll with the App's web.settings.

Deploying multiple Web Apps to Ubuntu

A common way for reliably hosting .NET Core Apps on Ubuntu 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).

Setup the deploy User Account

We'll start by creating a dedicated user account for hosting and running your .NET Core Apps to mitigate potential abuse. SSH into your Ubuntu server and create the deploy user account with a /home/deploy home directory and add them to the sudo group:

sudo useradd -m deploy
sudo usermod -aG sudo deploy

For seamless deployments use visudo to allow deploy to run supervisorctl without prompting for a password:

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL
%deploy ALL=(ALL:ALL) NOPASSWD: /usr/bin/supervisorctl
In vi type i to start editing a file and ESC to quit edit mode and :wq to save your changes before exiting.

Setup supervisor

Install supervisor using apt-get:

sudo apt-get install supervisor

You'll need to create a separate config file for each app in /etc/supervisor/conf.d/. We can use the same template below for all /apps by replacing bare with the name of the App:

/etc/supervisor/conf.d/web.bare.conf
[program:web-bare]
command=/usr/bin/dotnet /home/deploy/apps/web/app.dll web.bare.settings
directory=/home/deploy/apps/web
autostart=true
autorestart=true
stderr_logfile=/var/log/web-bare.err.log
stdout_logfile=/var/log/web-bare.out.log
environment=ASPNETCORE_ENVIRONMENT=Production
user=deploy
stopsignal=INT

Setup nginx

You'll also need to create a separate config for each website on nginx in /etc/nginx/sites-available/. 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/sites-available/bare.web-app.io
server {
    listen       80;
    server_name bare.web-app.io;

    location / {
        proxy_pass http://localhost:5001;
        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;
    }
}

You'll then need to create symlink for each website to tell nginx you want each website to be enabled:

ln -s /etc/nginx/sites-available/bare.web-app.io /etc/nginx/sites-enabled/bare.web-app.io

After this we can tell nginx to reload its configuration, as there's nothing listening to http://localhost:5001 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.

/etc/init.d/nginx 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.

Create the deployment script

We use sed in our deployment script to generate the release web.settings from each App's local web.settings that we save in the /web folder that gets deployed with our apps. The main configuration to change is to set debug false and to select a different port number which matches the port number used in its nginx configuration. We can then use rsync to efficiently copy all /app and /web folders to our remote server and use ssh to run a remote command instructing supervisorctl to restart all its Services:

deploy.apps.sh
cat ../apps/bare/web.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5001/" > ../apps/web/web.bare.settings
cat ../apps/redis/web.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5002/" > ../apps/web/web.redis.settings
cat ../apps/rockwind/web.sqlite.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5003/" > ../apps/web/web.rockwind-sqlite.settings
cat ../apps/rockwind-vfs/web.sqlite.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5004/" > ../apps/web/web.rockwind-vfs-sqlite.settings
cat ../apps/plugins/web.settings | sed "/debug/s/ .*/ false/" | sed "/port/s/ .*/ 5005/" > ../apps/web/web.plugins.settings
cat ../apps/chat/web.release.settings | sed "/port/s/ .*/ 5006/" > ../apps/web/web.chat.settings

rsync -avz -e 'ssh' ../apps deploy@web-app.io:/home/deploy

ssh deploy@web-app.io "sudo supervisorctl restart all"

Run the deployment script

To run the deployment script we need to make it executable before running it:

chmod +x deploy.apps.sh
./deploy.apps.sh

If all went well you'll be able to view each app using the domain name they've been configured with in nginx. Now whenever you need to redeploy again you'll only need to re-run ./deploy.apps.sh which will only copy the files that have changed before restarting all services.

You can also choose to restart only the app that was changed by specifying its program name in /etc/supervisor/conf.d/web.*.conf, e.g:

ssh deploy@web-app.io "sudo supervisorctl restart web-bare"

Using Travis CI to deploy using Docker to AWS ECS

A popular combination for deploying .NET Core Apps is to use the online Travis CI Continuous Integration Service to package your App in a Docker Container and deploy it to AWS ECS which takes care of the management and deployment of Docker instances over a configured cluster of EC2 compute instances.

The easiest way to set this up is to clone the Rockwind.Aws Web App which is preconfigured with a working scripts using Travis CI to package the Web App in a Docker container and deploy it to AWS ECS. In your local copy replace the /app folder with your App files then you'll then change the url in the Dockerfile to point to a plain-text copy of your App's web.settings, e.g:

Dockerfile
FROM microsoft/dotnet:2.0-sdk
COPY web /web
ADD https://raw.githubusercontent.com/NetCoreWebApps/Rockwind.Aws/master/app/web.settings /web/web.settings
WORKDIR /web
EXPOSE 5000/tcp
ENV ASPNETCORE_URLS https://*:5000
ENTRYPOINT ["dotnet", "/web/app.dll"]

The only other file that needs to change is deploy-envs.sh to configure it to use your App's deployment settings:

deploy-envs.sh
#!/bin/bash

# set environment variables used in deploy.sh and AWS task-definition.json:
export IMAGE_NAME=netcoreapps-rockwind-aws
export IMAGE_VERSION=latest

export AWS_DEFAULT_REGION=us-east-1
export AWS_ECS_CLUSTER_NAME=default
export AWS_VIRTUAL_HOST=rockwind-aws.web-app.io

# set any sensitive information in travis-ci encrypted project settings:
# required: AWS_ACCOUNT_ID, AWS_ACCESS_KEY, AWS_SECRET_KEY
# optional: SERVICESTACK_LICENSE

Setup AWS ECS and Travis CI

After configuring your App deployment scripts you'll then need to Setup your AWS ECS with an EC2 instance to deploy to and Create your project in Travis CI. You'll then need to add your AWS Account details in the Travis CI project using Secure Environment Variables to store your AWS_ACCOUNT_ID, AWS_ACCESS_KEY and AWS_SECRET_KEY as well as any sensitive info and connection strings your App uses.

made with by ServiceStack