Running an NGINX reverse proxy with Docker and Let's Encrypt on Compute Engine
Contributed by Google employees.
This tutorial guides you through running multiple websites on a Compute Engine instance using Docker. You secure the websites using free SSL/TLS certificates from Let's Encrypt.
Objectives
- Create a Compute Engine instance.
- Run an NGINX reverse proxy.
- Run multiple web applications in Docker.
- Install SSL/TLS certificates with Let's Encrypt.
Before you begin
- Create or select a Google Cloud project from the Cloud Console projects page.
- Enable billing for your project.
Costs
This tutorial uses billable components of Google Cloud including Compute Engine.
Use the Pricing Calculator to estimate the costs for your usage.
Setting up the virtual machine
Create a new Compute Engine instance using the CoreOS stable image. CoreOS comes with Docker pre-installed and supports automatic system updates.
- Open the Cloud Console.
- Create a new Compute Engine instance.
- Select the desired Zone, such as "us-central1-f".
- Select the desired Machine type, such as "micro" (f1-micro).
- Change the Boot disk to "CoreOS stable".
- Check the boxes to allow HTTP and HTTPS traffic in the Firewall section.
- Expand the Management, disk, networking section.
- Click the Networking tab.
- Select New static IP address under External IP.
- Give the IP address a name, such as "reverse-proxy".
- Click the Create button to create the Compute Engine instance.
Set up some domains for your instance
Create multiple A type DNS records for various domains/subdomains on your DNS provider pointing at the external IP address for your new instance.
For example, in Google Domains, open DNS for your domain, scroll to Custom resource records and add an A type record. The name "@" corresponds to the root of your domain or you can change it to a subdomain, such as "a" and "b".
This tutorial assumes that you have two subdomains with A records:
- a.example.com
- b.example.com
Setting up the reverse proxy
To have the separate websites respond only to their respective hosts, you use a reverse proxy. This tutorial uses the nginx-proxy Docker container to automatically configure NGINX to forward requests to the corresponding website.
As an example, this tutorial shows a plain NGINX server running as site A and a plain Apache server running as site B.
You run the commands in this section from the VM instance that you created in the "Setting up the virtual machine" section. Before you run these commands, connect to the VM instance using SSH.
Run the reverse proxy.
docker run -d \ --name nginx-proxy \ -p 80:80 \ -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
Start the container for site A, specifying the domain name in the
VIRTUAL_HOST
variable.docker run -d --name site-a -e VIRTUAL_HOST=a.example.com nginx
Check out your website at http://a.example.com.
With site A still running, start the container for site B.
docker run -d --name site-b -e VIRTUAL_HOST=b.example.com httpd
Check out site B at http://b.example.com.
Congratulations, you are running multiple apps on the same host using Docker and an nginx reverse proxy.
Note: If you do not wish to set up HTTPS for your websites using Let's Encrypt, you can skip reading the rest of this tutorial.
Setting up HTTPS with Let's Encrypt
Plain HTTP is not secure. It is not encrypted and is vulnerable to man-in-the-middle attacks. In this step, you'll add support for the HTTPS protocol.
Stop the containers.
docker stop site-a docker stop site-b docker stop nginx-proxy
Remove the containers.
docker rm site-a docker rm site-b docker rm nginx-proxy
To enable HTTPS via TLS/SSL, your reverse proxy requires cryptographic certificates. Use Let's Encrypt via the Docker Let's Encrypt nginx-proxy companion to automatically issue and use signed certificates.
Create a directory to hold the certificates.
cd mkdir certs
Run the proxy, but this time declaring volumes so that the Let's Encrypt companion can populate them with certificates.
docker run -d -p 80:80 -p 443:443 \ --name nginx-proxy \ -v $HOME/certs:/etc/nginx/certs:ro \ -v /etc/nginx/vhost.d \ -v /usr/share/nginx/html \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true \ jwilder/nginx-proxy
Run the Let's Encrypt companion container.
docker run -d \ --name nginx-letsencrypt \ --volumes-from nginx-proxy \ -v $HOME/certs:/etc/nginx/certs:rw \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ jrcs/letsencrypt-nginx-proxy-companion
Run site A.
In addition to
VIRTUAL_HOST
, specifyLETSENCRYPT_HOST
to declare the host name to use for the HTTPS certificate. Specify theLETSENCRYPT_EMAIL
so that Let's Encrypt can email you about certificate expirations.docker run -d \ --name site-a \ -e 'LETSENCRYPT_EMAIL=webmaster@example.com' \ -e 'LETSENCRYPT_HOST=a.example.com' \ -e 'VIRTUAL_HOST=a.example.com' nginx
You can watch the companion creator request new certificates by watching the logs.
docker logs nginx-letsencrypt
You should eventually see a log which says
Saving cert.pem
.After the certificate is issued, check out your website at https://a.example.com.
Run site B.
docker run -d \ --name site-b \ -e 'LETSENCRYPT_EMAIL=webmaster@example.com' \ -e 'LETSENCRYPT_HOST=b.example.com' \ -e 'VIRTUAL_HOST=b.example.com' httpd
You can watch the companion creator request new certificates by watching the logs.
docker logs nginx-letsencrypt
You should eventually see a log which says
Saving cert.pem
.After the certificate is issued, check out your website at https://b.example.com.
Congratulations, your web apps are now running behind an HTTPS reverse proxy.
Proxying composed web apps
In order to proxy the nginx-proxy
container and the web app container must be on
the same Docker network.
When you run a multi-container web app with docker-compose
, Docker attaches the
containers to a default network.
The default network is different from the bridge network that containers run with
the docker run
command attach to.
If you run the docker-compose
and have specified a VIRTUAL_HOST
environment variable in the docker-compose.yml
configuration file,
you'll see this error message in the docker logs nginx-proxy
output:
no servers are inside upstream
The proxy will also stop working. To resolve this,
Create a new Docker network.
docker network create --driver bridge reverse-proxy
Stop and remove your web application containers, the
nginx-proxy
container, and thenginx-letsencrypt
container.docker stop my-container docker rm my-container docker stop nginx-proxy docker rm nginx-proxy docker stop nginx-letsencrypt docker rm nginx-letsencrypt
Run the proxy and other containers, specifying the network with the
--net reverse-proxy
command-line parameter.Run the proxy container.
docker run -d -p 80:80 -p 443:443 \ --name nginx-proxy \ --net reverse-proxy \ -v $HOME/certs:/etc/nginx/certs:ro \ -v /etc/nginx/vhost.d \ -v /usr/share/nginx/html \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true \ jwilder/nginx-proxy
Run the Let's Encrypt helper container.
docker run -d \ --name nginx-letsencrypt \ --net reverse-proxy \ --volumes-from nginx-proxy \ -v $HOME/certs:/etc/nginx/certs:rw \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ jrcs/letsencrypt-nginx-proxy-companion
Run your website containers.
docker run -d \ --name site-a \ --net reverse-proxy \ -e 'LETSENCRYPT_EMAIL=webmaster@example.com' \ -e 'LETSENCRYPT_HOST=a.example.com' \ -e 'VIRTUAL_HOST=a.example.com' nginx
Modify the
docker-compose.yml
file to include the network you created in the networks definition.networks: reverse-proxy: external: name: reverse-proxy back: driver: bridge
In the container definitions, specify the appropriate networks. Only the web server needs to be on the reverse-proxy network. The other containers can stay on their own network.
The final
docker-compose.yml
file will look something like this:version: '2' services: db: restart: always image: my_database networks: - back web: restart: always image: my_webserver networks: - reverse-proxy - back environment: - VIRTUAL_PORT=1234 - VIRTUAL_HOST=c.example.com - LETSENCRYPT_HOST=c.example.com - LETSENCRYPT_EMAIL=webmaster@example.com networks: reverse-proxy: external: name: reverse-proxy back: driver: bridge
Run the
docker-compose up -d
command to run your composed containers with the new configuration.
Surviving reboots
When your Compute Engine instance restarts, the Docker containers will not
automatically restart. Use the --restart
flag for the docker run
command to
specify a Docker restart
policy.
I suggest always
or unless-stopped
so that Docker restarts the containers
on reboot.
Next steps
Running many web apps on a single host behind a reverse proxy is an efficient way to run hobby applications. To make your experience even better,
Note that apps deployed to a single instance are not highly available. For example, your applications will not be available during a system reboot.
To see how to run an app which requires high availability or scaling to many queries per second, try out some more scalable ways of hosting.
- Deploy a scalable web app using App Engine flexible environment.
- Host a static website using Firebase Hosting.
- Host a static website using Cloud Storage.
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates.