Clean Development URLs and SSL with Docker and Nginx Proxy
When developing microservices with docker containers, I often find myself trying to remember all of the different ports exposed by each service. Fortunately, there is a way to provide memorable, clean urls for each of your service endpoints.
By leveraging an Nginx Proxy in one of your containers, we can easily assign subdomains (e.g. service-a.mydomain.com) to each of your microservice projects.
I'll also show you how to dynamically generate a development certificate and load it into the proxy so that you will be able to connect securely to your services with SSL.
Source Files
All files are available in the repository: https://github.com/christophla/blog-docker-nginx-proxy
Both Bash
and PowerShell
scripts are provided for Windows, OSX, and Linux.
The Docker Compose File
Jason Wilder has created a great docker image, jwilder/nginx-proxy, that contains an instance of Nginx that automatically wires up a reverse proxy with host-headers for any docker containers that include the environmental variable VIRTUAL_HOST
.
In the sample code, I've included a dotnet core webapi project as a service, along with an instance of nginx-proxy. The service nginx-proxy-app
sets its virtual host as myapp.nginx-proxy-app.com
.
version: '3'
services:
nginx-proxy-app:
container_name: nginx-proxy-app
image: nginx-proxy-app
build:
context: .
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=development
- REMOTE_DEBUGGING=${REMOTE_DEBUGGING}
- VIRTUAL_HOST=myapp.nginx-proxy-app.com
ports:
- "5000:80"
networks:
- dev-network
tty: true
stdin_open: true
nginx-proxy-nat:
image: jwilder/nginx-proxy
container_name: nginx-proxy-nat
environment:
- HSTS=off
- HTTPS_METHOD=noredirect
ports:
- "80:80"
- "443:443"
networks:
- dev-network
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./certs:/etc/nginx/certs
networks:
dev-network:
driver: bridge
The Setup Script
Our script is going to have to perform several things in order to setup our project to be able to use hostnames and SSL. First, we will remove any existing certificates generated for our custom domain. This will allow us to generate a new certificate when we add or remove services (and hostnames) to our development environment. Next, we will generate a certificate and add it to the docker volume ./certs:/etc/nginx/certs
so that Nginx can pick it up for SSL. Last, we will add our hostnames to the local machine etc/hosts
file..
setupProxy () {
# remove existing certificates
echo -e "${YELLOW} Removing existings certificates... ${RESTORE}"
sudo security delete-certificate -c $certificatePrefix /Library/Keychains/System.keychain
# generate key
openssl \
genrsa \
-out certs/$certificatePrefix.key \
4096
# generate csr request
openssl \
req \
-new \
-sha256 \
-out certs/$certificatePrefix.csr \
-key certs/$certificatePrefix.key \
-config openssl-san.conf
#generate certificate from csr request
openssl \
x509 \
-req \
-days 3650 \
-in certs/$certificatePrefix.csr \
-signkey certs/$certificatePrefix.key \
-out certs/$certificatePrefix.crt \
-extensions req_ext \
-extfile openssl-san.conf
# generate pem
cat certs/$certificatePrefix.crt certs/$certificatePrefix.key > certs/$certificatePrefix.pem
# install certificate
if [ -f certs/$certificatePrefix.crt ]; then
echo -e "${YELLOW} Installing certificate... ${RESTORE}"
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain certs/$certificatePrefix.crt
else
echo -e "${RED} An error occurred while generating the certificate: certs/$certificatePrefix.crt ${RESTORE}"
fi
# Write Hosts
addHost "myapp.nginx-proxy-app.com"
}
Since Chrome 58, self signed wildcard certificates are not supported without Subject Alternative Names (SANs). As a result, we will will use the config file openssl-san.conf
to include each of our service hostnames.
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C=US
ST=Texas
L=Austin
O=Development
OU=Development Domain
emailAddress=admin@nginx-proxy-app.com
CN = nginx-proxy-app.com
[ req_ext ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = myapp.nginx-proxy-app.com
The Setup Task
We are going to create a VS Code Task to make running our script easier from within the IDE.
{
"version": "2.0.0",
"tasks": [{
"label": "setup",
"type": "shell",
"osx": {
"command": "bash ./scripts/project-tasks.sh setup"
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "dedicated"
},
"problemMatcher": [],
"windows": {
"command": ".\\scripts\\project-tasks.ps1 -Setup"
}
}]
}
Conclusion
At this point, you should able to launch your docker-compose project and the Nginx service will automatically wire up your other services as hostnames with SSL.
Source Code
A fully-functional project is available at: