I am trying to setup a certificate for a locally running react app on a virtual host local.example.com
. This has to just work locally on docker setup. After going through some articles, I came up with this docker-compose.yml:
version: "3"
services:
mongo:
image: mongo
restart: always
ports:
- "27017:27017"
volumes:
- mongodbdata:/data/db
networks:
- proxy
mongo-express:
image: mongo-express
restart: always
ports:
- "8081:8081"
networks:
- proxy
react:
build:
context: ./client
dockerfile: ./Dockerfile
ports:
- "3001:3001"
stdin_open: true
volumes:
- ./client:/client
- /client/node_modules
labels:
# this enables traefik for your service
- "traefik.enable=true"
# this defines the url, traefik will get the ssl certificate for
- "traefik.http.routers.myapplication.rule=Host(`local.example.com`)"
# this tells traefik to use https to access the website
- "traefik.http.routers.myapplication.entrypoints=websecure"
# this tells traefik to use the certresolver, that we defined above for resolving tls (in our case letsencrypt)
- "traefik.http.routers.myapplication.tls.certresolver=myresolver"
# this let's us forward the port we set above. Change this to the port you expose in your application (3000, 4000, ...) or remove the line, if your application already exposes port 80/443
- "traefik.http.services.myapplication.loadbalancer.server.port=3000"
depends_on:
- "server"
networks:
- proxy
server:
build:
context: ./server
dockerfile: ./Dockerfile
ports:
- "5001:5001"
volumes:
- traefik.toml:/traefik.toml
- acme.json:/acme.json
- ./server:/server
- /server/node_modules
labels:
# this enables traefik for your service
- "traefik.enable=true"
# this defines the url, traefik will get the ssl certificate for
- "traefik.http.routers.myapplication.rule=Host(`local.example.com`)"
# this tells traefik to use https to access the website
- "traefik.http.routers.myapplication.entrypoints=websecure"
# this tells traefik to use the certresolver, that we defined above for resolving tls (in our case letsencrypt)
- "traefik.http.routers.myapplication.tls.certresolver=myresolver"
# this let's us forward the port we set above. Change this to the port you expose in your application (3000, 4000, ...) or remove the line, if your application already exposes port 80/443
- "traefik.http.services.myapplication.loadbalancer.server.port=5001"
depends_on:
- "mongo"
networks:
- proxy
whoami:
image: "containous/whoami"
container_name: "myapplication"
restart: unless-stopped
ports:
- "4000:4000"
networks:
- proxy
traefik:
image: "traefik:v2.2"
container_name: "traefik"
command:
# this can be uncommented to get more information, in case something doesn't work
- "--log.level=DEBUG"
# set this to true to get access to the traefik web interface unter http://YOURIP:8080
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # uncomment this line to only test ssl generation first (to make sure you don't run into letsencrypt limits)
- "--certificatesresolvers.myresolver.acme.email=kamlekar.venkatesh@gmail.com"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080" # this is used for the web interface, that let's you check and monitor traefik and your configuration. It's very nice for debugging your config - only available if "api.insecure" above is set to true
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
- proxy
# The following is only necessary if you want to enforce https!
# if you don't need that, you can just remove the labels here
labels:
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
networks:
proxy:
external: true
volumes:
mongodbdata:
traefik.toml:
acme.json:
On doing docker-compose up
, I am seeing the following error in traefik
service. Also, though the react app service is running in docker but I hit https://local.example.com:3001
, is unreachable
time="2022-01-23T08:30:52Z" level=error msg="Unable to obtain ACME certificate for domains "local.example.com": unable to generate a certificate for the domains [local.example.com]: error: one or more domains had a problem:\n[local.example.com] acme: error: 400 :: urn:ietf:params:acme:error:dns :: DNS problem: NXDOMAIN looking up A for local.example.com - check that a DNS record exists for this domain; DNS problem: NXDOMAIN looking up AAAA for local.example.com - check that a DNS record exists for this domain, url: \n" providerName=myresolver.acme routerName=myapplication@docker rule="Host(
local.example.com
)"
Here are the research notes which I tried till now (These notes for only my personal understanding so could be not in detail)
You can start from this fiddle: https://github.com/kamlekar/react-docker-ssl-virtualhost
You need to use TLS for your local setup. The host you need a certificate for is local.example.com
. There is no way to obtain a certificate from Letsencrypt
for this name, because you're not controlling the example.com
domain. One of the ways Letsencrypt
creates a certificate is a challenge - you prove that you own the domain by creating a TXT DNS record. If you own a domain you can do that, but your case is different, because you only need this for local development.
However, you can just use openssl
to generate a self signed certificate for whichever domain name you want. This is a good reference on how to do this. You can use the local.example.com
domain name for the generated certificate. If you're successful, you'll end up with the certificate and it's private key. Note where you save those files, as you'll need them. Keep in mind that the certificate is self-signed, so your browser will give you a warning, unless you add this certificate to the trust store of your operating system.
The next step in your case is to make Traefik use those self signed certificates when serving content from your application. I think this answer has a good example of that.
After having this, you'll only need to edit your hosts file and redirect your localhost:8080
(the port on which your Traefik serves your application) to local.example.com
.
Also, Traefik is not the only solution for your case. You can also achieve the same using Nginx, for example. Choose which one satisfies your use case. My suggestion would be to use the one that's easiest to configure, because it's for local development. Here's the first result I got when searching for a nginx docker-compose self-signed certificate
.
UPDATE
Here's a quick example of what I'm describing above.
First generate the certificate:
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -sha256 -days 1000 -subj '/CN=local.example.com'
You'll end up with two files in your current directory (key.pem and cert.pem). Now create the nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 443 ssl;
server_name local.example.com;
ssl_certificate cert.pem;
ssl_certificate_key key.pem;
ssl_session_timeout 5m;
location / {
proxy_pass http://myapp:8080;
proxy_set_header Host $host;
proxy_read_timeout 1800;
proxy_connect_timeout 1800;
}
}
}
And now the docker-compose.yaml file:
version: "3"
services:
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./cert.pem:/etc/nginx/cert.pem
- ./key.pem:/etc/nginx/key.pem
ports:
- "443:443"
networks:
- local-dev-01
myapp:
image: your-react-app-image
command: "command-that-starts-your-app"
networks:
- local-dev-01
networks:
local-dev-01:
Docker creates a network called local-dev-01
for you, which allows both services to be able to resolve each other by their name. That's why we have myapp:8080 in the nginx.conf. We also mount the configuration and the generated certificate and key for local.example.com
.
The final step is to edit your hosts
file and add the following line:
127.0.0.1 local.example.com
After that, you should have no trouble reaching your application on https://local.example.com
on your machine. Keep in mind that your browser will keep warning you that the certificate is self-signed, so you should add it as an exception.