Setup a Reverse Proxy Server using Nginx, ASP.NET Core and Docker - Part I
You probably have heard about reverse proxy servers. Your DevOps team may have already configured your company’s website to use a reverse proxy server. But what is it exactly?
Well, to answer that, let’s see what a proxy server is. A proxy server is a computer program running on a machine that lies between a client and the Internet and provides extra security and anonymity. A reverse proxy server provides the same features for a protected server.
Proxy Server vs Reverse Proxy Server
Reverse proxy servers provide the following features:
-
Load Balancing - Reverse Proxy Servers can control incoming requests and reroute them to a designated group of servers. Distributing load on multiple servers increases speed and capacity utilization.
-
Web Acceleration - Reverse proxy servers can provide features like data compression, caching, SSL encryption, etc., and leaving upstream servers free to do what they are supposed to do.
-
Security and Anonymity - By sitting in front of the internal network, reverse proxy servers can add another layer of protection against security attacks.
We will have a demonstration for each feature described above but first, let’s set up a reverse proxy server on our local machine.
If you’re a DotNet Core developer, you probably know that Kestrel is a cross-platform web server included and enabled by default in ASP.NET Core apps. Nginx is a well-known web server with a rich feature set. In this blog post, we will use Nginx as a reverse proxy web server for the Kestrel and ASP.NET Core.
Let’s start with building a simple web application. Create a folder called reverse-proxy-sample
that contains another folder named app
:
1
2
mkdir reverse-proxy-sample && cd reverse-proxy-sample
mkdir app && cd app
Inside the app
folder run dotnet new mvc --no-https
. This will create a basic ASP.NET Core MVC application without any support for HTTPS. To be able to run the app inside docker, create the following files:
A Dockerfile
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM mcr.microsoft.com/dotnet/sdk:5.0 as build-env
ENV ASPNETCORE_ENVIRONMENT="Production"
WORKDIR /app
COPY . ./
RUN dotnet publish -c Release -o /app/out
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "app.dll"]
It’s a good practice to have a .dockerignore
file too. It helps to make smaller Docker images. You can
download a dotnet version of it from here.
Return to the reverse-proxy-sample
root and create a folder named nginx
:
1
mkdir nginx && cd nginx
Inside the nginx
folder create a config
folder. Inside it, create a default.conf
file with the following content:
1
2
3
4
5
6
7
8
9
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
proxy_pass http://app:3500/;
}
}
Thats it! This file configures Nginx to redirect any incoming request to http://app:3500. Now back to the nginx
folder and create a Dockerfile
for it:
1
2
3
FROM nginx:1.19.9
COPY ./config/default.conf /etc/nginx/conf.d/default.conf
Here when we run the Nginx docker image, the default configuration will be replaced with the configuration above.
It’s time to run the images. Go to the load-balance-sample
directory and create a docker-compose.yml
file with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.4'
services:
app:
build:
context: ./app
ports:
- "3800:3500"
environment:
- ASPNETCORE_URLS=http://+:3500
nginx:
build:
context: ./nginx
ports:
- "3000:80"
Then run docker-compose up
and wait for the Docker to download and build images. At the end you should see something like this in the terminal:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
... (lots of text about docker build process...)
Creating reverse-proxy-sample_app_1 ...
Creating reverse-proxy-sample_nginx_1 ...
Creating reverse-proxy-sample_nginx_1 ... done
Creating reverse-proxy-sample_app_1 ... done
Attaching to reverse-proxy-sample_nginx_1, reverse-proxy-sample_app_1
nginx_1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx_1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
nginx_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
nginx_1 | 10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
nginx_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
nginx_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
nginx_1 | /docker-entrypoint.sh: Configuration complete; ready for start up
app_1 | warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
app_1 | Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
app_1 | warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
app_1 | No XML encryptor configured. Key {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} may be persisted to storage in unencrypted form.
app_1 | info: Microsoft.Hosting.Lifetime[0]
app_1 | Now listening on: http://[::]:3500
app_1 | info: Microsoft.Hosting.Lifetime[0]
app_1 | Application started. Press Ctrl+C to shut down.
app_1 | info: Microsoft.Hosting.Lifetime[0]
app_1 | Hosting environment: Production
app_1 | info: Microsoft.Hosting.Lifetime[0]
app_1 | Content root path: /app
You can directly open the application at http://localhost:3800. By doing so, you’re directly interacting with the Kestrel, the default web server provided by ASP.NET Core. If you open http://localhost:3000 you will see the same application but your request went through the Nginx and re-routed to the Kestrel. Here Nginx acts as a reverse proxy web server.
The following diagram shows the big picture: Calling application directly vs Calling through Reverse Proxy Web Server
I hope you find this helpful. In the next blog post, I will implement a sample for every feature described earlier for the reverse proxy web server.