Introduction
Are you fed up with using localhost and a range of different ports when developing locally, having difficulty with testing content security policies, having to configure TLS for a range of different server applications, and using different URLs when running applications on your laptop natively vs in docker?
Using a reverse proxy can solve all of the above.
You can clone the code I’ve written for this article via this GitHub repository.
Outcome
We will configure Nginx server to run on ports 80/443 via docker, proxying traffic to your web application running natively or in docker.
TLS will be configured in Nginx meaning you will only need to do certificate-related config in Nginx.
Code
Nginx configuration
Firstly we’ll configure Nginx to forward requests to your local site(s), this will need to run on port 80/443 to make the developer experience better as browsers will allow you to omit the port. i.e. you can use https://my-website.com
instead of https://my-website.com:port
.
The following server
code block will tell Nginx to listen to requests to my-website.com
on ports 80 and 443.
Additional to proxying HTTP requests it has some config to support larger requests, the reason I’ve included this is because ideally, we don’t want Nginx to block requests that your original server would have served.
proxy_pass http://host.docker.internal:1337/;
is where we tell Nginx where to proxy the request to. It should use host.docker.internal
for the address not localhost
as localhost
when running the proxy in a container will not communicate with the host or another container.
As you add more servers into the proxy I’d recommend adding the shared config into another .conf
file and including it rather than repeating it in every server
and/or location
block.
1server {
2 listen 80;
3 listen 443 ssl;
4
5 # Support Large Requests
6 proxy_buffer_size 128k;
7 proxy_buffers 4 256k;
8 proxy_busy_buffers_size 256k;
9 large_client_header_buffers 4 16k;
10
11 server_name my-website.com;
12
13 # Load Certificates
14 ssl_certificate /etc/ssl/certs/my-cert.crt;
15 ssl_certificate_key /etc/ssl/certs/my-cert.key;
16
17 location / {
18 # Site we want to proxy to
19 proxy_pass http://host.docker.internal:1337/;
20 proxy_pass_request_headers on;
21
22 # Support Large Requests
23 fastcgi_buffers 16 16k;
24 fastcgi_buffer_size 32k;
25
26 # Headers supporting proxying
27 proxy_set_header Host $host;
28 proxy_set_header X-Real-IP $remote_addr;
29 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
30 proxy_set_header X-Forwarded-Server $host;
31 proxy_set_header X-Forwarded-Host $host:$server_port;
32 proxy_set_header X-Forwarded-Proto https;
33
34 # Headers to proxy websockets
35 proxy_set_header Upgrade $http_upgrade;
36 proxy_set_header Connection "Upgrade";
37 }
38}
Trusted root certificate
Next, we’ll do the necessary configuration to set up TLS for the Nginx reverse proxy. The reason we need to do this is so that the browser doesn’t complain when we navigate to our site using HTTPS, we need our machine to trust the certificates being provided by Nginx.
The following assumes you have git for windows installed (which comes with OpenSSL bundled as part of it). If you’re not using git for windows then you’ll need to install OpenSSL.
The following Powershell will create an x509 certificate and import it into your trusted certificate store.
1New-Item -ItemType Directory -Force -Path ./certs
2
3$env:Path += ";C:\Program Files\Git\usr\bin"; #Use this when you have git for windows installed
4
5openssl genrsa -des3 -out ./certs/root-ca.key 2048
6openssl req -x509 -new -nodes -key ./certs/root-ca.key -sha256 -days 1825 -out ./certs/root-ca.pem -subj "/C=GB/ST=Somewhere/L=Somewhere/O=MyOrg/OU=IT/CN=my-website.com"
7
8Import-Certificate -FilePath ./certs/root-ca.pem -CertStoreLocation Cert:\LocalMachine\Root
Certificates for your site(s)
Next, we’ll create the relevant certificates for the domains that you want to use for your sites locally in this example we’ll generate a wildcard cert, and a cert for my-website.com
this will allow you to run sites like admin.my-website.com
and my-website.com
etc.
Create a file called certificate.ext
and add the following content, this is used when generating the certificates.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.my-website.com
DNS.2 = my-website.com
1New-Item -ItemType Directory -Force -Path ./docker/certs
2
3$domains = @("*.my-website.com", "my-website.com");
4
5# Generate certs for the above domains, signing them with our own root cert
6
7$certExtFile = "./certs/certificate.ext"
8$dockerCertPath = "./docker/certs"
9$rootCaPath = "./certs";
10
11$env:Path += ";C:\Program Files\Git\usr\bin"; #Use this when you have git for windows installed
12
13foreach($domain in $domains)
14{
15 $domainFile = $domain.Replace("*", "");
16
17 openssl genrsa -out "$dockerCertPath/$domainFile.key" 2048;
18 openssl req -new -key "$dockerCertPath/$domainFile.key" -out "$dockerCertPath/$domainFile.csr" -subj "/C=GB/ST=Somewhere/L=Somewhere/O=MyOrg/OU=IT/CN=$domain";
19 openssl x509 -req -in "$dockerCertPath/$domainFile.csr" -CA "$rootCaPath/root-ca.pem" -CAkey "$rootCaPath/root-ca.key" -CAcreateserial -out "$dockerCertPath/$domainFile.crt" -days 825 -sha256 -extfile $certExtFile;
20}
Dockerfile & Docker compose
Here we’re going to create the related config necessary for running the above Nginx reverse proxy via Docker.
The following Dockerfile
will run a stable Nginx server inside of a Linux alpine container, and the docker-compose.yml
file makes it easy for you to run it and provide it with certificates via a volume
.
1FROM nginx:stable-alpine as run
2
3RUN rm -rf /usr/share/nginx/html/*
4
5COPY *.conf /etc/nginx/conf.d/
The following docker-compose file will run the reverse proxy, and two example sites to demonstrate the reverse proxy
1name: reverse-proxy-example
2
3networks:
4 dev-network:
5 name: dev-network
6 driver: bridge
7
8services:
9 nginx-reverse-proxy:
10 restart: always
11 deploy:
12 resources:
13 limits:
14 memory: 200M
15 container_name: dev-reverse-proxy
16 build:
17 context: nginx
18 dockerfile: Dockerfile
19 ports:
20 - 80:80
21 - 443:443
22 networks:
23 - dev-network
24 volumes:
25 - ./certs/:/etc/ssl/certs
26
27 my-website:
28 restart: always
29 deploy:
30 resources:
31 limits:
32 memory: 200M
33 container_name: my-website
34 build:
35 context: sites
36 dockerfile: my-website.Dockerfile
37 ports:
38 - 1338:80
39 networks:
40 - dev-network
41
42 admin.my-website:
43 restart: always
44 deploy:
45 resources:
46 limits:
47 memory: 200M
48 container_name: admin.my-website
49 build:
50 context: sites
51 dockerfile: admin-my-website.Dockerfile
52 ports:
53 - 1337:80
54 networks:
55 - dev-network
Outro
Congratulations! Your local dev experience should now be more representative of your staging and production environments, and you shouldn’t need to use localhost
in your browser anymore when building websites locally.
This also provides a good foundation for more advanced development environment improvements like the ability to proxy to a cloud-hosted application.