HTTPS for Docker Compose with mkcert
Configure trusted HTTPS in Docker Compose development environments — certificate generation, Nginx and Caddy TLS termination, CA trust injection into containers, inter-service HTTPS, and multi-developer team setup.
Content
Overview
Docker containers do not inherit your host machine's trust store. When you run mkcert on your host and generate certificates, your browser trusts them because mkcert installed its CA into the host's trust store. But containers start with a clean slate — they do not know about your local CA. To use trusted HTTPS inside Docker Compose, you mount the certificates into a reverse proxy for TLS termination and inject the CA certificate into any container that needs to make outbound HTTPS calls to other local services.
Why Local HTTPS Matters
Browser and API behaviors differ between HTTP and HTTPS in ways that break real development:
- -Secure cookies — cookies with
SecureandSameSite=Noneattributes only work over HTTPS. OAuth flows, session management, and cross-site authentication require them. - -OAuth callbacks — Google, GitHub, and most OAuth providers reject non-HTTPS redirect URIs (except localhost in some cases).
- -Mixed content — browsers block HTTP requests from HTTPS pages. If your production frontend is HTTPS, your development frontend should be too.
- -WebSocket Secure —
wss://requires TLS. Testing WebSocket features locally needs HTTPS. - -Service Workers — only register on HTTPS origins (except localhost).
- -HTTP/2 — browsers require TLS for HTTP/2 connections.
Step 1: Generate Certificates
The certificate covers all listed domains and IPs. Include localhost and 127.0.0.1 as fallbacks. Add every custom domain your services use.
Step 2: Configure /etc/hosts
On Windows, the hosts file is at C:\Windows\System32\drivers\etc\hosts. On macOS/Linux, it is /etc/hosts.
Step 3: Nginx TLS Termination
Nginx handles TLS and proxies to your application containers over plain HTTP:
Alternative: Caddy (Zero-Config TLS)
Caddy's configuration is significantly simpler than Nginx for TLS:
Step 4: Docker Compose Configuration
Step 5: CA Trust for Inter-Service HTTPS
If your API container makes HTTPS calls to other local services (or to itself through the proxy), it needs to trust the mkcert CA. Without this, you get UNABLE_TO_VERIFY_LEAF_SIGNATURE or certificate verify failed errors.
Node.js Containers
The NODE_EXTRA_CA_CERTS environment variable tells Node.js to trust additional CA certificates beyond the built-in set. This is the correct approach — never use NODE_TLS_REJECT_UNAUTHORIZED=0, which disables all TLS verification.
Python Containers
Go Containers
System-Level CA Trust (Any Language)
For containers where environment variables are not sufficient, update the system trust store in the Dockerfile:
Note the .crt extension — update-ca-certificates requires it on Debian-based images.
Team Setup with Environment Variables
Different developers have their mkcert CA in different locations. Use environment variables to make the setup portable:
Document the setup in your project README:
Verification
Gitignore Configuration
The certificate and key files are development-only and should not be committed. The Nginx/Caddy config and Compose file are safe to commit — they reference file paths, not secrets.
Best Practices
- -Use a reverse proxy (Nginx or Caddy) for TLS termination — do not configure TLS in individual application containers.
- -Mount certificates and CA as read-only (
:ro) volumes. - -Mount only
rootCA.peminto containers that need CA trust — never mountrootCA-key.pem. The CA key can sign arbitrary certificates. - -Use
NODE_EXTRA_CA_CERTSfor Node.js,REQUESTS_CA_BUNDLE/SSL_CERT_FILEfor Python, andSSL_CERT_FILEfor Go. These are the standard mechanisms each runtime provides. - -Never set
NODE_TLS_REJECT_UNAUTHORIZED=0— it disables all TLS verification, not just for local certificates. - -Keep certificates in a gitignored
certs/directory. Document the generation command in your README. - -Use environment variables for the CA root path so the setup works across macOS, Linux, and Windows developers.
Common Pitfalls
- -Mounting
rootCA-key.peminto containers — this is the CA's private key. Anyone with it can mint trusted certificates. Only mountrootCA.pem(the public certificate). - -Using
NODE_TLS_REJECT_UNAUTHORIZED=0instead of proper CA trust — this mask hides real TLS issues and often leaks into production configurations. - -Forgetting to update
/etc/hosts— browsers resolve custom domains via DNS, andmyapp.localdoes not resolve without a hosts entry. - -Not mounting certificates as read-only — containers should not be able to modify certificates.
- -Committing
.pemfiles to Git — they are development artifacts, not project code. Gitignore the entirecerts/directory. - -Running
mkcert -installinside Docker — mkcert needs to modify the host trust store. Always run it on the host machine.
FAQ
Discussion
Loading comments...