Set a local web development environment with custom Urls and HTTPS

With web projects running inside Docker containers.

François Romain

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.

Prerequisites
I tested on Mac. It should work on other platforms, but I didn’t try.
- Have Docker on our computer.
- Several web projects run inside Docker containers on our local machine. (Here is an example)


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.local

Configure 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-stopped
networks:
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-proxy

Start the service

In the Terminal:

docker-compose up -d

This 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-proxy network.

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/hosts

Navigate with the arrows to the end of the file and add the line:

127.0.0.1        my-app.local

Type 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: 3000

VIRTUAL_PORT is the port the web server is listening to.

2. Expose ports

services:
my-app:

expose:
- 3000

The 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-proxy

Now lets start the service with:

$ cd /my-app.local
$ docker-compose up

The 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.crt and ssl.key to my-app.local.crt and my-app.local.key.

Trust the certificate

  • Add the my-app.local.crt file 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, select Always trust and 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.


  1. web project: a generic word for web-app, website, web-server… You name it.

François Romain

Written by

Freelance developer / designer → http://francoisromain.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade