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.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-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-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
andssl.key
tomy-app.local.crt
andmy-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
, selectAlways 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.
- web project: a generic word for web-app, website, web-server… You name it.