Hey, in this post series, I will explain how you can have a full development environment in HTTPS with a valid certificate for your local development domain.
Why should I use HTTPS in development?
In fact, there are many reasons you should use HTTPS for your development environment:
- To use Secure Cookie for authentication (using Auth service such as Keycloack, Auth0, ...) → See more here or here
- To have the development & production environment as similar as possible. See the 10th factor of the 12-factor app.
- Some external services (SaaS) required HTTPS (i.e. for webhooks)
Setup the certificate
We need to setup an certificate signed with a rootCA which is recognized by our system and browser.
First we will choose an hostname for our local development environment, then we will use the awesome tool mkcert to generate a valid certificate.
Choose a dev hostname
Choose an hostname for your development environment.
⚠️ Don't use existing domain name which could create conflicts!
You can use the usual localhost or another hostname. I like to use dev.local which I will take for the next steps.
You will need to add the hostname in your local dns which is /etc/hosts
in Linux & Mac. So add 127.0.0.1 dev.local
in your /etc/hosts
.
💡 Tips: I'm using this script from this gist to add & remove hostname easily 👍
🗒️ Note: If you use sub-domain such as api.dev.local
, app.dev.local
, ... You have to add them in /etc/hosts
too. You can't use wildcard such as *.dev.local
directly on /etc/hosts
but you can use a service such as dnsmasq to achieve this.
Mkcert
We will use mkcert to manage the rootCA & generate our certificate.
Installation
🍏 MacOS
On MacOS, use Homebrew
brew install mkcert
brew install nss # if you use Firefox
🐧 Linux
On Linux, first install certutil
.
sudo apt install libnss3-tools
curl -Lo /tmp/mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64
chmod +x /tmp/mkcert
sudo mv /tmp/mkcert /usr/local/bin/mkcert
See more installation methods here 👌
Usage
First we need to install the local CA to the system and browsers.
$ mkcert -install
Created a new local CA at "/home/***/.local/share/mkcert" 💥
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊
Next, we will use mkcert to generate our certificate. Using my example domain dev.local
& the wildcard *.dev.local
.
$ mkcert -cert-file certs/local-cert.pem -key-file certs/local-key.pem dev.local *.dev.local
Using the local CA at "/home/***/.local/share/mkcert" ✨
Created a new certificate valid for the following names 📜
- "dev.local"
- "*.dev.local"
Reminder: X.509 wildcards only go one level deep, so this won't match a.b.dev.local ℹ️
The certificate is at "certs/local-cert.pem" and the key at "certs/local-key.pem" ✅
We can now use the certificate located at certs/local-cert.pem
& certs/local-key.pem
.
Next we will see how we will use this certificate for different apps.
BONUS: Wrapping everything in a Makefile for a dev-stack
here is how I organized my dev-stack, it's highly opinionated so take only what you need from it 😉!
📁 Folders Structure
project_root
├── dev-stack
│ ├── certs
│ │ ├── .gitignore
│ │ ├── local-cert.pem
│ │ └── local-key.pem
│ ├── scripts
│ │ ├── get-ip.sh
│ │ └── manage-hosts.sh
│ ├── .env.local
│ ├── .gitignore
│ ├── docker-compose.yml
│ ├── Makefile
│ └── README.md
Makefile
ifndef DEV_STACK_DIR
DEV_STACK_DIR = $(CURDIR)
endif
SCRIPTS_DIR=${DEV_STACK_DIR}/scripts
ifndef HOSTNAME
HOSTNAME = dev.local
endif
ifndef SUBDOMAINS
SUBDOMAINS = docs \
traefik \
mail \
media \
portainer \
graphql \
auth
endif
ifndef DATABASE
DATABASE = postgres
endif
ifndef INFRA
INFRA = traefik \
maildev \
minio \
mkdocs \
portainer \
${DATABASE} \
graphql-engine \
keycloak \
auth-connector
endif
export HOST_IP := $(shell ${SCRIPTS_DIR}/get-ip.sh)
# HELP
.PHONY: help
help: ## List of the command available, make {command}
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.DEFAULT_GOAL := help
start: ## Start the docker stack
docker-compose up -d ${INFRA}
up: ## Start the docker stack
docker-compose up ${INFRA}
stop: ## Stop the docker stack
docker-compose stop
restart: ## Restart the docker stack
docker-compose restart
down: ## Down the docker stack and remove all containers and networks
docker-compose down
build: ## Build or rebuild all docker container
docker-compose build
pull: ## Pull latest image
docker-compose pull
add-hosts: ## Add Hosts entries for Dev stack
${SCRIPTS_DIR}/manage-hosts.sh addhost ${HOSTNAME}
$(foreach subdomain, $(SUBDOMAINS), ${SCRIPTS_DIR}/manage-hosts.sh addhost $(subdomain).$(HOSTNAME);)
remove-hosts: ## Remove Hosts entries for Dev stack
${SCRIPTS_DIR}/manage-hosts.sh removehost ${HOSTNAME}
$(foreach subdomain, $(SUBDOMAINS), ${SCRIPTS_DIR}/manage-hosts.sh removehost $(subdomain).$(HOSTNAME);)
certs-generate: ## Generate certs for all our domains
mkcert -install
mkcert -cert-file certs/local-cert.pem -key-file certs/local-key.pem $(HOSTNAME) *.$(HOSTNAME)
certs-uninstall: ## Uninstall the local CA (but do not delete it)
mkcert -uninstall
Scripts
get-ip.sh
#!/bin/bash
# Get host IP address
if [ "$(uname)" = "Darwin" ];then
ifconfig en0 | grep "inet "| cut -d ' ' -f 2
else
ip route get 1.2.3.4 | awk '{print $7}'
fi
manage-hosts.sh
#!/bin/bash
# copy from https://gist.github.com/irazasyed/a7b0a079e7727a4315b9
# PATH TO YOUR HOSTS FILE
ETC_HOSTS=/etc/hosts
# DEFAULT IP FOR HOSTNAME
IP="127.0.0.1"
# Hostname to add/remove.
HOSTNAME=$2
removehost() {
echo "removing host";
if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
then
echo "$HOSTNAME Found in your $ETC_HOSTS, Removing now...";
sudo sed -i".bak" "/$HOSTNAME/d" $ETC_HOSTS
else
echo "$HOSTNAME was not found in your $ETC_HOSTS";
fi
}
addhost() {
echo "adding host";
HOSTS_LINE="$IP\t$HOSTNAME"
if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
then
echo "$HOSTNAME already exists : $(grep $HOSTNAME $ETC_HOSTS)"
else
echo "Adding $HOSTNAME to your $ETC_HOSTS";
sudo -- sh -c -e "echo '$HOSTS_LINE' >> /etc/hosts";
if [ -n "$(grep $HOSTNAME /etc/hosts)" ]
then
echo "$HOSTNAME was added succesfully \n $(grep $HOSTNAME /etc/hosts)";
else
echo "Failed to Add $HOSTNAME, Try again!";
fi
fi
}
$@
Usage of the certificate
→ See in next posts how you can now use this new generated certificates
- Local HTTPS for Angular app in Nx workspace (or angular cli)
- Local HTTPS for React app in Nx workspace
- Local HTTPS for Express app (api) in Nx workspace
- Local HTTPS for NestJS app (api) in Nx workspace
- Local HTTPS for Docker services using Traefik
Github repository
Nightbr / full-https-development-environment
A full development environment in HTTPS with a valid certificate for your local development domain with mkcert, Nx workspace, angular, reactjs, nestjs, express, docker, traefik.
Myorg
This project was generated using Nx.
🔎 Nx is a set of Extensible Dev Tools for Monorepos.
Adding capabilities to your workspace
Nx supports many plugins which add capabilities for developing different types of applications and different tools.
These capabilities include generating applications, libraries, etc as well as the devtools to test, and build projects as well.
Below are our core plugins:
-
React
npm install --save-dev @nrwl/react
- Web (no framework frontends)
npm install --save-dev @nrwl/web
-
Angular
npm install --save-dev @nrwl/angular
-
Nest
npm install --save-dev @nrwl/nest
-
Express
npm install --save-dev @nrwl/express
-
Node
npm install --save-dev @nrwl/node
There are also many community plugins you could add.
Generate an application
Run nx g @nrwl/react:app my-app
to generate an application.
You can use any of the plugins above to generate applications as well.
When using Nx, you can create multiple applications and libraries in the same workspace.
Generate a library
Run nx
…
Top comments (2)
I just released lodev, single binary that provides easy setup for local development env with SSL (HTTPS).
It will create local CA, install it as a trusted CA and generate certificate and key. Spin up small DNS server that is used only to resolve dev.lo domain and create reverse proxy on port 443 under
https://dev.lo
domain and by default proxy all requests tohttp://localhost:3000
, target port can be changed.Awesome series, this is a slick setup. Thank you for bringing Nx to my attention.