In one of my previous articles I made brief introduction to Docker – containerization technology which gives more control over infrastructure. Suppose that we have number of .NET sites running on Kestrel in Linux and published via nginx to the internet like shown on the following schema (ports numbers are only for example here):
And we want to move all our infrastructure to Docker (backend, database, nginx itself, etc) so it will look like this:
On practice however this switch will take time and at some time period we will have both sites running still in the Linux host and sites running in Docker containers, and we will need to serve both types:
Since only one app may listen to 80 port we need to decide whether we want to keep nginx service running on the host or move nginx to Docker and configure it to serve both sites from the host and sites from Docker containers. In this article I will describe this last option.
Since nginx is running in Docker there won’t be many problems with hosting sites running also in Docker. Just run these sites and nginx container in the same network:
#docker-compose-nginx.yml name: myservice services: nginx: image: nginx:latest ports: - 80:80 … networks: - mynetwork
and nginx will be able to resolve containers by names. I.e. if site’s container name is mysite-web-1 you may specify nginx.conf like this:
server { listen 80; listen [::]:80; server_name mysite.ru www. mysite.ru; location / { proxy_pass http://mysite-web-1:80; … } }
However with sites running in host it is not that straightforward. Since nginx is running in Docker container which has own IP address we need to instruct it to which IP it should forward requests if they came for sites running on the host.
We can do that by adding special extra host host.docker.internal to nginx docker compose:
name: myservice services: nginx: image: nginx:latest ports: - 80:80 restart: always networks: - mynetwork extra_hosts: - host.docker.internal:host-gateway
If we will check /etc/hosts file inside nginx container we will see that host.docker.internal points to 172.17.0.1 IP address which is default IP used by Docker for host:
docker exec -it myservice-nginx-1 sh more /etc/hosts … host.docker.internal 172.17.0.1
Next step is to instruct nginx to forward request to the host if it came for site running there. For doing that we need to modify nginx.conf and specify host.docker.internal with appropriate port in proxy_pass property:
server { listen 80; listen [::]:80; server_name mysite.ru www.mysite.ru; location / { proxy_pass http://host.docker.internal:5000; … } }
However that is still not enough. If your .NET site is running on Kestrel you most probably configured to run it as a daemon via the following commands:
systemctl enable mysite.ru.service systemctl start mysite.ru.service
where mysite.ru.service file contains the following line:
Environment=ASPNETCORE_URLS=http://localhost:5000
With our current configuration if we will open shell inside our nginx container and try to reach our site via curl we will get Connection refused:
docker exec -it myservice-nginx-1 sh curl -X GET http://host.docker.internal:5000 Connection refused
The problem here is that Kestrel is currently listening only for localhost IP (127.0.0.1). We can check it on the host using the following command:
netstat -tulpn | grep 5000 tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 22496/dotnet tcp6 0 0 ::1:5000 :::* LISTEN 22496/dotnet
but request from nginx container goes to 172.17.0.1. To solve that we need to modify /etc/systemd/system/mysite.ru.service and add http://172.17.0.1:5000 to ASPNETCORE_URLS after semicolon:
Environment=ASPNETCORE_URLS=http://localhost:5000;http://172.17.0.1:5000
and reload the service:
systemctl stop mysite.ru.service systemctl daemon-reload systemctl start mysite.ru.service systemctl status mysite.ru.service
After that check that it listen 5000 port also on 172.17.0.1:
netstat -tulpn | grep 5000 tcp 0 0 172.17.0.1:5000 0.0.0.0:* LISTEN 22496/dotnet tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 22496/dotnet tcp6 0 0 ::1:5000
And now if we go inside container shell and try to reach the site connection should be successful:
docker exec -it myservice-nginx-1 sh curl -X GET http://host.docker.internal:5000 Connection successful
which means that nginx is now able to serve sites running both in containers and on the host itself.