When working on web projects it is often useful and recommended to enable SSL for your development environment. For example if your project works with cookies, it is likely that the server sets the Secure attribute, ensuring that they only sent to the server over HTTPS. But even without cookies it's a good idea to try to minimize differences between your development and production environments. Fortunately, using Docker that can be done done easily in just a few steps.
mkcert is a command line tool that makes it ridiculously simple to generate locally trusted certificates, for development.
mkcert, as documented in the readme. In my case, running Windows, it's just a choco install mkcertmkcert -installmkcert elborai.me localhostThe frontend or backend projects are unlikely to handle the SSL protocol themselves, instead the common approach is to run them behind a reverse proxy. That way we can also share the same port for both, which simplifies the whole process as we won't face CORS issues.
Caddy is the simplest web server and reverse proxy to setup that I've ever faced. The configuration is done with a Caddyfile, that would look something like this:
# specify the domain you want to use for your local development
localhost {
# use the certificate made with mkcert
tls /root/certs/elborai.me.pem /root/certs/elborai.me-key.pem
# forward requests from which the path starts with /v1 to whatever is listening on api:5001
reverse_proxy /v1/* http://api:5001
# forward all other requests to webui:5000
reverse_proxy http://webui:5000
}
By default Caddy already handles automatic forwarding from HTTP to HTTPS, so we don't have to do anything extra for that to work.
And to run it, the following docker-compose.yml does the trick:
version: "3.8"
services:
webui:
# ... assuming webui is already configured
depends_on:
- api
api:
# ... assuming api is already configured
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
depends_on:
- webui
- api
volumes:
# ./dev/Caddyfile is where the config file is located
- ./dev/Caddyfile:/etc/caddy/Caddyfile
# ./dev/certs is where the mkcert certificate is located
- ./dev/certs:/root/certs
# persist Caddy's data in a volume
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Assuming both api and webui have been setup to work in docker-compose, a docker-compose up webui should already setup the reverse proxy, the API, and the web UI.
We can either run the dev server in Docker, or run it locally on the host machine and direct Caddy to the host's IP address.
The dev server requires 3 environment variables:
HOST=0.0.0.0, Caddy will need to reach Rollup's web server so we should be sure that it binds to the correct network interfaceTLS_KEY=../dev/certs/elborai.me-key.pem, the certificate keyTLS_CERT=../dev/certs/elborai.me.pem, the certificateAnd the rollup.config.js can be updated to enable SSL for the livereload server:
// ./webui/rollup.config.js
const production = !process.env.
// get the certificate and key location from env vars 👇
// TLS certificates
const tlsCert = process.env ? process.env. : ""
const tlsKey = process.env ? process.env. : ""
// ...
plugins: ,
// ...
The simplest way to run the dev server on the host with the correct config is to use an .env file:
# .env file
HOST=0.0.0.0
TLS_KEY=../dev/certs/elborai.me-key.pem
TLS_CERT=../dev/certs/elborai.me.pem
Then use the npm module dotenv at the top of your Rollup config file:
dotenv. // inject the content of the .env file into 'process.env'
When running the dev server on the host, be sure to update the Caddyfile to target the host's IP (in my case, 192.168.0.81):
localhost {
# ...
# forward all other requests to host, port 5000
reverse_proxy http://192.168.0.81:5000
}
A basic Dockerfile to run the dev env looks like this:
# Rollup server
# Livereload
Here we can note:
/sourcelivereload part of Rollup needs access to the certificate we generated with mkcert to be able to work with SSL, so we also mount a volume at /root/certs5000 is used by Rollup itself, while 35729 is the default port for the livereload pluginHOST and PORT are the two environment variables used to control the Rollup web server. When something run within Docker it should always use the interface 0.0.0.0 to be available from the outsideinotify support under Windows seems to break quite often), but that works well under Linux (tested via WSL2) and macOS (at least for me).To use it in docker-compose.yml, that would look like this:
services:
webui:
build: ./webui # directory that contains the Dockerfile
environment:
- HOST=0.0.0.0
# path within the docker container
- TLS_KEY=/root/certs/elborai.me-key.pem
- TLS_CERT=/root/certs/elborai.me.pem
# here you can overwrite the command to run
command: npm run dev
ports:
- "5000:5000" # Rollup web server
- "35729:35729" # Livereload
volumes:
# ./webui is the path to the source directory
- ./webui:/source
# ./dev/certs is the directory that contains the certificate and key
- ./dev/certs:/root/certs
Generate the certificate in ./dev/certs using mkcert.
Then, the docker-compose.yml:
version: "3.8"
services:
webui:
build: ./webui
environment:
- HOST=0.0.0.0
- TLS_KEY=/root/certs/elborai.me-key.pem
- TLS_CERT=/root/certs/elborai.me.pem
command: npm run dev
ports:
- "5000:5000"
- "35729:35729"
volumes:
- ./webui:/source
- ./dev/certs:/root/certs
depends_on:
- api
api:
build: ./services
ports:
- "5001:5001"
environment:
- HTTP_ADDR=0.0.0.0:5001
- POSTGRES_URL=postgres://postgres:password@db:5432/default?sslmode=disable
restart: unless-stopped
depends_on:
- db
db:
image: postgres:12-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_DB=default
- POSTGRES_PASSWORD=password
- PGDATA=/var/lib/postgresql/data/pgdata
volumes:
- db_data:/var/lib/postgresql/data
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
environment:
- HOST_IP=192.168.0.81
- APP_DOMAIN=localhost
- CERTS_DOMAIN=elborai.me
volumes:
- ./dev/Caddyfile:/etc/caddy/Caddyfile
- ./dev/certs:/root/certs
- caddy_data:/data
- caddy_config:/config
depends_on:
- webui
- api
volumes:
db_data:
caddy_data:
caddy_config:
The reverse proxy and SSL endpoint configuration, ./dev/Caddyfile:
{$APP_DOMAIN} {
log {
output stdout
format console
}
tls /root/certs/{$CERTS_DOMAIN}.pem /root/certs/{$CERTS_DOMAIN}-key.pem
reverse_proxy /v1/* http://api:5001
# if webui running directly on host
reverse_proxy http://{$HOST_IP}:5000
# if webui running within docker-compose
# reverse_proxy http://webui:5000
}
The Svelte Dockerfile, at ./webui/Dockerfile:
# Rollup server
# Livereload
The Rollup config file, at ./webui/rollup.config.js:
const production = !process.env.
// TLS certificates
const tlsCert = process.env ? process.env. : ""
const tlsKey = process.env ? process.env. : ""