Books / The Little ASP.NET Core Book / Chapter 15
Deploy the application
You’ve come a long way, but you’re not quite done yet. Once you’ve created a great application, you need to share it with the world!
Because ASP.NET Core applications can run on Windows, Mac, or Linux, there are a number of different ways you can deploy your application. In this chapter, I’ll show you the most common (and easiest) ways to go live.
In this chapter
Deployment options
ASP.NET Core applications are typically deployed to one of these environments:
-
A Docker host. Any machine capable of hosting Docker containers can be used to host an ASP.NET Core application. Creating a Docker image is a very quick way to get your application deployed, especially if you’re familiar with Docker. (If you’re not, don’t worry! I’ll cover the steps later.)
-
Azure. Microsoft Azure has native support for ASP.NET Core applications. If you have an Azure subscription, you just need to create a Web App and upload your project files. I’ll cover how to do this with the Azure CLI in the next section.
-
Linux (with Nginx). If you don’t want to go the Docker route, you can still host your application on any Linux server (this includes Amazon EC2 and DigitalOcean virtual machines). It’s typical to pair ASP.NET Core with the Nginx reverse proxy. (More about Nginx below.)
-
Windows. You can use the IIS web server on Windows to host ASP.NET Core applications. It’s usually easier (and cheaper) to just deploy to Azure, but if you prefer managing Windows servers yourself, it’ll work just fine.
Kestrel and reverse proxies
If you don’t care about the guts of hosting ASP.NET Core applications and just want the step-by-step instructions, feel free to skip to one of the next two sections.
ASP.NET Core includes a fast, lightweight web server called Kestrel. It’s the server you’ve been using every time you ran dotnet run
and browsed to http://localhost:5000
. When you deploy your application to a production environment, it’ll still use Kestrel behind the scenes. However, it’s recommended that you put a reverse proxy in front of Kestrel, because Kestrel doesn’t yet have load balancing and other features that more mature web servers have.
On Linux (and in Docker containers), you can use Nginx or the Apache web server to receive incoming requests from the internet and route them to your application hosted with Kestrel. If you’re on Windows, IIS does the same thing.
If you’re using Azure to host your application, this is all done for you automatically. I’ll cover setting up Nginx as a reverse proxy in the Docker section.
Deploy to Azure
Deploying your ASP.NET Core application to Azure only takes a few steps. You can do it through the Azure web portal, or on the command line using the Azure CLI. I’ll cover the latter.
What you’ll need
- Git (use
git --version
to make sure it’s installed) - The Azure CLI (follow the install instructions at https://github.com/Azure/azure-cli)
- An Azure subscription (the free subscription is fine)
- A deployment configuration file in your project root
Create a deployment configuration file
Since there are multiple projects in your directory structure (the web application, and two test projects), Azure won’t know which one to publish. To fix this, create a file called .deployment
at the very top of your directory structure:
.deployment
[config]
project = AspNetCoreTodo/AspNetCoreTodo.csproj
Make sure you save the file as .deployment
with no other parts to the name. (On Windows, you may need to put quotes around the filename, like ".deployment"
, to prevent a .txt
extension from being added.)
If you ls
or dir
in your top-level directory, you should see these items:
.deployment
AspNetCoreTodo
AspNetCoreTodo.IntegrationTests
AspNetCoreTodo.UnitTests
Set up the Azure resources
If you just installed the Azure CLI for the first time, run
az login
and follow the prompts to log in on your machine. Then, create a new Resource Group for this application:
az group create -l westus -n AspNetCoreTodoGroup
This creates a Resource Group in the West US region. If you’re located far away from the western US, use az account list-locations
to get a list of locations and find one closer to you.
Next, create an App Service plan in the group you just created:
az appservice plan create -g AspNetCoreTodoGroup -n AspNetCoreTodoPlan --sku F1
F1 is the free app plan. If you want to use a custom domain name with your app, use the D1 ($10/month) plan or higher.
Now create a Web App in the App Service plan:
az webapp create -g AspNetCoreTodoGroup -p AspNetCoreTodoPlan -n MyTodoApp
The name of the app (MyTodoApp
above) must be globally unique in Azure. Once the app is created, it will have a default URL in the format: http://mytodoapp.azurewebsites.net
Deploy your project files to Azure
You can use Git to push your application files up to the Azure Web App. If your local directory isn’t already tracked as a Git repo, run these commands to set it up:
git init
git add .
git commit -m "First commit!"
Next, create an Azure username and password for deployment:
az webapp deployment user set --user-name nate
Follow the instructions to create a password. Then use config-local-git
to spit out a Git URL:
az webapp deployment source config-local-git -g AspNetCoreTodoGroup -n MyTodoApp --out tsv
https://[email protected]/MyTodoApp.git
Copy the URL to the clipboard, and use it to add a Git remote to your local repository:
git remote add azure <paste>
You only need to do these steps once. Now, whenever you want to push your application files to Azure, check them in with Git and run
git push azure master
You’ll see a stream of log messages as the application is deployed to Azure.
When it’s complete, browse to http://yourappname.azurewebsites.net to check out the app!
Deploy with Docker
If you aren’t using a platform like Azure, containerization technologies like Docker can make it easy to deploy web applications to your own servers. Instead of spending time configuring a server with the dependencies it needs to run your app, copying files, and restarting processes, you can simply create a Docker image that describes everything your app needs to run, and spin it up as a container on any Docker host.
Docker can make scaling your app across multiple servers easier, too. Once you have an image, using it to create 1 container is the same process as creating 100 containers.
Before you start, you need the Docker CLI installed on your development machine. Search for “get docker for (mac/windows/linux)” and follow the instructions on the official Docker website. You can verify that it’s installed correctly with
docker version
Add a Dockerfile
The first thing you’ll need is a Dockerfile, which is like a recipe that tells Docker what your application needs to build and run.
Create a file called Dockerfile
(no extension) in the root, top-level AspNetCoreTodo
folder. Open it in your favorite editor. Write the following line:
FROM microsoft/dotnet:2.0-sdk AS build
This tells Docker to use the microsoft/dotnet:2.0-sdk
image as a starting point. This image is published by Microsoft and contains the tools and dependencies you need to execute dotnet build
and compile your application. By using this pre-built image as a starting point, Docker can optimize the image produced for your app and keep it small.
Next, add this line:
COPY AspNetCoreTodo/*.csproj ./app/AspNetCoreTodo/
The COPY
command copies the .csproj
project file into the image at the path /app/AspNetCoreTodo/
. Note that none of the actual code (.cs
files) have been copied into the image yet. You’ll see why in a minute.
WORKDIR /app/AspNetCoreTodo
RUN dotnet restore
WORKDIR
is the Docker equivalent of cd
. This means any commands executed next will run from inside the /app/AspNetCoreTodo
directory that the COPY
command created in the last step.
Running the dotnet restore
command restores the NuGet packages that the application needs, defined in the .csproj
file. By restoring packages inside the image before adding the rest of the code, Docker is able to cache the restored packages. Then, if you make code changes (but don’t change the packages defined in the project file), rebuilding the Docker image will be super fast.
Now it’s time to copy the rest of the code and compile the application:
COPY AspNetCoreTodo/. ./AspNetCoreTodo/
RUN dotnet publish -o out /p:PublishWithAspNetCoreTargetManifest="false"
The dotnet publish
command compiles the project, and the -o out
flag puts the compiled files in a directory called out
.
These compiled files will be used to run the application with the final few commands:
FROM microsoft/dotnet:2.0-runtime AS runtime
ENV ASPNETCORE_URLS http://+:80
WORKDIR /app
COPY --from=build /app/AspNetCoreTodo/out ./
ENTRYPOINT ["dotnet", "AspNetCoreTodo.dll"]
The FROM
command is used again to select a smaller image that only has the dependencies needed to run the application. The ENV
command is used to set environment variables in the container, and the ASPNETCORE_URLS
environment variable tells ASP.NET Core which network interface and port it should bind to (in this case, port 80).
The ENTRYPOINT
command lets Docker know that the container should be started as an executable by running dotnet AspNetCoreTodo.dll
. This tells dotnet
to start up your application from the compiled file created by dotnet publish
earlier. (When you do dotnet run
during development, you’re accomplishing the same thing in one step.)
The full Dockerfile looks like this:
Dockerfile
FROM microsoft/dotnet:2.0-sdk AS build
COPY AspNetCoreTodo/*.csproj ./app/AspNetCoreTodo/
WORKDIR /app/AspNetCoreTodo
RUN dotnet restore
COPY AspNetCoreTodo/. ./
RUN dotnet publish -o out /p:PublishWithAspNetCoreTargetManifest="false"
FROM microsoft/dotnet:2.0-runtime AS runtime
ENV ASPNETCORE_URLS http://+:80
WORKDIR /app
COPY --from=build /app/AspNetCoreTodo/out ./
ENTRYPOINT ["dotnet", "AspNetCoreTodo.dll"]
Create an image
Make sure the Dockerfile is saved, and then use docker build
to create an image:
docker build -t aspnetcoretodo .
Don’t miss the trailing period! That tells Docker to look for a Dockerfile in the current directory.
Once the image is created, you can run docker images
to to list all the images available on your local machine. To test it out in a container, run
docker run --name aspnetcoretodo_sample --rm -it -p 8080:80 aspnetcoretodo
The -it
flag tells Docker to run the container in interactive mode (outputting to the terminal, as opposed to running in the background). When you want to stop the container, press Control-C.
Remember the ASPNETCORE_URLS
variable that told ASP.NET Core to listen on port 80? The -p 8080:80
option tells Docker to map port 8080 on your machine to the container’s port 80. Open up your browser and navigate to http://localhost:8080 to see the application running in the container!
Set up Nginx
At the beginning of this chapter, I mentioned that you should use a reverse proxy like Nginx to proxy requests to Kestrel. You can use Docker for this, too.
The overall architecture will consist of two containers: an Nginx container listening on port 80, forwarding requests to the container you just built that hosts your application with Kestrel.
The Nginx container needs its own Dockerfile. To keep it from conflicting with the Dockerfile you just created, make a new directory in the web application root:
mkdir nginx
Create a new Dockerfile and add these lines:
nginx/Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
Next, create an nginx.conf
file:
nginx/nginx.conf
events { worker_connections 1024; }
http {
server {
listen 80;
location / {
proxy_pass http://kestrel:80;
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;
}
}
}
This configuration file tells Nginx to proxy incoming requests to http://kestrel:80
. (You’ll see why kestrel
works as a hostname in a moment.)
When you make deploy your application to a production environment, you should add the
server_name
directive and validate and restrict the host header to known good values. For more information, see:
https://github.com/aspnet/Announcements/issues/295
Set up Docker Compose
There’s one more file to create. Up in the root directory, create docker-compose.yml
:
docker-compose.yml
nginx:
build: ./nginx
links:
- kestrel:kestrel
ports:
- "80:80"
kestrel:
build: .
ports:
- "80"
Docker Compose is a tool that helps you create and run multi-container applications. This configuration file defines two containers: nginx
from the ./nginx/Dockerfile
recipe, and kestrel
from the ./Dockerfile
recipe. The containers are explicitly linked together so they can communicate.
You can try spinning up the entire multi-container application by running:
docker-compose up
Try opening a browser and navigating to http://localhost (port 80, not 8080!). Nginx is listening on port 80 (the default HTTP port) and proxying requests to your ASP.NET Core application hosted by Kestrel.
Set up a Docker server
Specific setup instructions are outside the scope of this book, but any modern flavor of Linux (like Ubuntu) can be used to set up a Docker host. For example, you could create a virtual machine with Amazon EC2, and install the Docker service. You can search for “amazon ec2 set up docker” (for example) for instructions.
I like using DigitalOcean because they’ve made it really easy to get started. DigitalOcean has both a pre-built Docker virtual machine, and in-depth tutorials for getting Docker up and running (search for “digitalocean docker”).