Nginx Reverse Proxy to ASP.NET Core – Same Docker Container

As mentioned in the previous post, it is recommended to use something other than Kestrel as the front-line web server. In this example, I’m going to show how to use Nginx as a reverse proxy to the ASP.NET Core application.

Reverse-proxy architecture options

In terms of architecture setup for reverse proxying for ASP.NET Core in Docker there are a couple of options.

The first option is to run Nginx within the same container as the ASP.NET Core application. This is the option shown in this post.

The other option is to run Nginx as a separate Docker container. In that setup, the communication would be proxied over a network bridge between the Nginx container and the containers hosting the application. This second approach will be shown in the next post.

Creating the application

Again for this example, I’ll just be using the default web application generated by the .NET Core CLI.

To create that application, run the following commands:

mkdir app
cd app
dotnet new -t web
dotnet restore
dotnet build

And the publish the application (to package it up for the Docker container):

dotnet publish

Building the image

The Docker image will be based on the microsoft/aspnetcore:1.0 image used previously. It will be modified to include Nginx. That will allow Nginx to use a fairly standard reverse proxy configuration while also running the Kestrel process.

Dockerfile configuration

Create a Dockerfile and include the following contents:

FROM microsoft/aspnetcore:1.0

RUN apt-get update
RUN apt-get install -y nginx

WORKDIR /app
COPY bin/Debug/netcoreapp1.0/publish .

COPY ./startup.sh .
RUN chmod 755 /app/startup.sh

RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx

ENV ASPNETCORE_URLS http://+:5000
EXPOSE 5000 80

CMD ["sh", "/app/startup.sh"]

Let’s deconstruct what’s going on there…

Line 1 specifies that the image will use the same microsoft/aspnetcore:1.0 base as previous examples.

Lines 3-4 install the nginx package (and all of its dependencies) from the Debian package management store.

Lines 6-7 setup the /app directory and place the ASP.NET Core application within that directory.

Lines 9-10 setup a start-up script (explained in greater detail below).

Lines 12-13 replace the out-of-the-box Nginx configuration file with a custom configuration.

Line 15 configures Kestrel to listen to port 5000.

Line 16 exposes port 80 to the outside world.

Line 18 invokes the start-up script that we’ll define shortly.

Nginx configuration

Now we need to create the nginx.conf file referenced in the Dockerfile. In the same directory, create nginx.conf and place the following contents into it:

worker_processes 4;

events { worker_connections 1024; }

http {
    sendfile on;

    upstream app_servers {
        server 127.0.0.1:5000;
    }

    server {
        listen 80;

        location / {
            proxy_pass         http://app_servers;
            proxy_redirect     off;
            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-Host $server_name;
        }
    }
}

Once again, let’s deconstruct a few parts of the file.

Lines 8-10 define a group of servers (in this case one). This named resource (app_servers) can be used elsewhere in the file for proxying.

Line 13 tells Nginx to listen on port 80.

Lines 15-22 indicate that all traffic should be proxied to app_servers.

In summary, this file configures Nginx to listen on port 80 and then redirect all traffic to port 5000 on the same box. Given the ASPNETCORE_URLS variable specified in the Dockerfile, this means that the traffic will be proxied from Nginx to Kestrel.

Start-up script

There can only be a single CMD used (or ENTRYPOINT defined) for a Docker container. But this reverse proxy configuration needs both to start Nginx and Kestrel. To do this, we’ll use a shell script to perform both tasks.

Create a startup.sh file in the same directory with the following contents:

#!/bin/bash
service nginx start
dotnet /app/app.dll

Building the image

Build the image with the following command:

docker build -t example/aspnetcore-nginx .

As you watch the output, you’ll see that Nginx is being installed:

...
Step 3/7 : RUN apt-get install -y nginx
 ---> Running in ca67ca6606e8
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  fontconfig-config fonts-dejavu-core geoip-database init-system-helpers
  libalgorithm-c3-perl libarchive-extract-perl libcgi-fast-perl libcgi-pm-perl
...

Running the image

To test the image, run an instance of it:

docker run --name test -d -p 8080:80 example/aspnetcore-nginx

You should now be able to reach the application at http://localhost:8080.

You can verify that it’s running the Nginx service through curl:

$ curl -s -D - localhost:8080 -o /dev/null
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Fri, 24 Feb 2017 14:39:51 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding

If you open a shell on the box itself, you can look at both the Nginx and Kestrel services individually.

docker exec -it test bash

# curl -s -D - localhost:80 -o /dev/null
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Fri, 24 Feb 2017 14:45:03 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked

# curl -s -D - localhost:5000 -o /dev/null
HTTP/1.1 200 OK
Date: Fri, 24 Feb 2017 14:45:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Kestrel

Other options?

As I mentioned above, there are two main ways to setup a reverse proxy architecture for ASP.NET Core applications. In the next post, I’ll show the alternate setup that keeps Nginx and ASP.NET Core in separate Docker containers.

Comments
  • shahanur_reza@hotmail.com

    Hi,
    The following script does not start nginx. I have tried a number of a different approach.

    #!/bin/bash
    service nginx start
    dotnet /app/app.dll

    Did this work for you? or have you had to manually start nginx?
    Please, help.

  • Aaron Alexander

    The whole idea behind that script was to start both the ASP.NET Core service as well as the reverse proxy in the same command. That way both services would start when the Docker container was started.

    In terms of whether it worked for me, that script did work when I put together this series of blog posts. I haven’t actually used this approach in quite a while, however, instead preferring moving Nginx to a separate container and using a reverse proxy to the Kestrel service (https://www.sep.com/sep-blog/2017/02/27/nginx-reverse-proxy-to-asp-net-core-separate-docker-containers/).

    If you want to keep trying this approach, what do the Docker logs contain for the output of the command?

    Also, you could try looking at the line-ending characters in the script. Since you’re pushing this script into a Linux container, you may have better luck using LF instead of CRLF line endings for the bash script. I know I’ve run into that before when moving shell scripts into Docker containers.

Add your comment

Your email address will not be published.