Set a local web development environment with custom Urls and HTTPS
With web projects running inside Docker containers.
To develop a web project¹ on a local machine, we usually launch a web-server listening at http://localhost:port.
In production, the url for this project is https://my-app.com.
We want to mimic the production environment on the local computer. How can we have a custom url and HTTPS, like https://my-app.local?
In this article, we are going to:
- Create custom urls for several web projects, like
my-app.local. - Set a reverse-proxy to route the requests to these urls to specific Docker containers.
- Add HTTPS with self-signed certificates.
Set a reverse proxy
We want to route requests to custom urls (like my-app.local) to specific web-servers. This is the role of a reverse proxy.
We are going to use nginx as a reverse-proxy. We could manually setup and configure nginx, but this has some downside:
- The need to modify the nginx config every time a website is added or removed.
- The need to restart nginx after each config modification.
- The need to expose a port of each container to the host, and therefore keep track of the used ports (two containers can not use the same port).
To avoid these downsides, the magic Docker service jwilder/nginx-proxy (by Jason Wilder) automates the creation of nginx configs and reloads the proxy server when a container starts and stops. And it has HTTPS support.
Directory structure
Create a nginx-proxy directory next to the the web project directories. On my computer, I have a Sites directory in my user root where all my projects are stored with a .local extension.
Inside this nginx-proxy directory, create a docker-compose.yml file and a certs directory. This structure looks like this:
.
+-- nginx-proxy
| +-- docker-compose.yml
| +-- certs
+-- my-app.local
+-- my-other-app.local
+-- my-cool-website.localConfigure the nginx-proxy Docker service
In the docker-compose.yml file, paste this content:
version: "3.1"services:
nginx-proxy:
image: jwilder/nginx-proxy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./certs:/etc/nginx/certs
- /var/run/docker.sock:/tmp/docker.sock:ro
restart: unless-stoppednetworks:
default:
external:
name: nginx-proxy
This files creates a single Docker service called nginx-proxy which uses the jwilder/nginx-proxy image and share its ports 80 and 443 with the host. This service belongs to a docker-network called nginx-proxy. Before starting this service, we need to create the network.
Create the Docker network
In the Terminal:
docker network create nginx-proxyStart the service
In the Terminal:
docker-compose up -dThis uses the docker-compose.yml file to launch the nginx-proxy service which creates and starts a container.
The nginx-proxy is now running and listens on ports 80 and 443 (for HTTPS). If the computer reboots, the nginx-proxy will start automatically with Docker for Mac. Now, we can forget about it: there is no need to change anything in the above configuration to link a new web project.
Configure a custom url for each web project
To make a web project work with a custom url, we need to:
- Make its url point to
localhost. - Add its Docker service to the
nginx-proxynetwork.
Make an url point to localhost
To have a custom url like my-app.local pointing to localhost, we need to modifiy the /etc/host file. In the terminal, edit the hosts file with the nano texteditor.
sudo nano /etc/hostsNavigate with the arrows to the end of the file and add the line:
127.0.0.1 my-app.localType ctrl + x, then y to save and exit nano. Now, the custom url points to localhost.
We still need to tell the nginx proxy to route a request to this url to a specific Docker container.
Link a web project to the nginx-proxy
We have a web project with a Docker configuration (like this one for example).
To link this project to the running nginx-proxy, we need to update its own docker-compose.yml file (not the one from nginx-proxy above) with a few instructions:
1. Environment variables
services:
my-app:
…
environment:
VIRTUAL_HOST: my-app.local
VIRTUAL_PORT: 3000VIRTUAL_PORT is the port the web server is listening to.
2. Expose ports
services:
my-app:
…
expose:
- 3000The exposed port value is the same as the VIRTUAL_PORT above.
Also, we should remove any existing PORTS instruction as we don’t want to share the ports outside the network.
3. Network
networks:
default:
external:
name: nginx-proxyNow lets start the service with:
$ cd /my-app.local
$ docker-compose upThe app is now listening at http://my-app.local.
Add HTTPS
Create a self-signed SSL certificate for a custom domain
To create the certificates, we use the create-ssl-certificate command line tool (by Christian Alfoni). First we install it globally with npm i -g create-ssl-certificate.
- go to
/nginx-proxy/certs/. - issue a certificate with
create-ssl-certificate --hostname my-app --domain local. - Rename the files
ssl.crtandssl.keytomy-app.local.crtandmy-app.local.key.
Trust the certificate
- Add the
my-app.local.crtfile to the Keychain Access app. - In Keychain Access, click on the certificate name to open a popup.
- In this popup, click on the small arrow in front of
Trust. - In
When using this certificate, selectAlways trustand close the popup.
Now Chrome trusts the certificate. Firefox is a bit more picky and we have to explicitly trust the certificate when prompted.
Finally our app is listening at https://my-app.local!
Next steps
In a previous article, I went through the steps to do something similar on an online server. This is useful to replicate our local configuration on a production environment.
- web project: a generic word for web-app, website, web-server… You name it.
