HTTPS Deployment
Overview
ft-configs-ui is served in production by nginx:alpine inside a single container (see Docker & Deploy). HTTPS is optional and decided automatically at container startup based on whether TLS material is mounted — no HTTPS_ENABLED flag exists anymore.
docker/ops/entrypoint.sh inspects /etc/nginx/ssl/tls.crt and /etc/nginx/ssl/tls.key:
-
Both files present →
docker/ops/nginx.conf.templateis rendered → nginx listens on 80 and 443 with TLS. -
Either file missing →
docker/ops/nginx-http-only.conf.templateis rendered → nginx listens on 80 only.
The entrypoint logs which template it selected. The two templates are identical in every respect except the listen directives and TLS block; locations (/configs-service/, /health, /ready, /nginx_status, SPA fallback, cache rules, source-map block) are shared.
There is no Node.js server in the image and no HTTP monkey-patching. nginx handles TLS termination natively.
Prerequisites
-
A TLS certificate in PEM format (file name
tls.crt— see "Why those filenames" below). -
The matching private key in PEM format (file name
tls.key). -
(Optional) A CA bundle for clients/backends that need to trust corporate or self-signed CAs — mount it wherever your tooling expects; nginx itself does not need it for serving the SPA.
Quick Start (Docker)
Run the image with the cert directory bind-mounted into /etc/nginx/ssl:
# Assumes you have tls.crt and tls.key in ./certs/ on the host
docker run --rm \
-p 80:80 \
-p 443:443 \
-v "$PWD/certs:/etc/nginx/ssl:ro" \
-e BACKEND_URL="http://ft-configs-service:8080" \
ft-configs-ui:local
Expected stdout (paraphrased):
[entrypoint] TLS material found at /etc/nginx/ssl → using nginx.conf.template
[entrypoint] wrote /usr/share/nginx/html/runtime-config.js
Verify:
curl -k https://localhost/health # → {"status":"ok"}
curl http://localhost/health # still served — nginx listens on 80 as well
For local development without real certificates, use docker/generate-cert.sh to produce a self-signed tls.crt / tls.key pair into ./certs/, then run the command above. Browsers will show a trust warning until you import the cert into your trust store.
HTTP-only mode (K8s with Ingress TLS)
Omit the volume mount entirely — entrypoint.sh will fall back to the HTTP-only template:
docker run --rm \
-p 80:80 \
-e BACKEND_URL="http://ft-configs-service:8080" \
ft-configs-ui:local
Expected stdout:
[entrypoint] No TLS material at /etc/nginx/ssl → using nginx-http-only.conf.template
This is the recommended mode behind Kubernetes Ingress, AWS ALB, or any reverse proxy that terminates TLS upstream.
Docker Compose
services:
ft-configs-ui:
image: ft-configs-ui:local
ports:
- "80:80"
- "443:443"
environment:
BACKEND_URL: "http://ft-configs-service:8080"
METRICS_ALLOW_IP: "10.0.0.0/8"
volumes:
- ./certs:/etc/nginx/ssl:ro
To run HTTP-only, remove the 443:443 mapping and the volumes: entry.
TLS configuration
Taken from docker/ops/nginx.conf.template:
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/tls.crt;
ssl_certificate_key /etc/nginx/ssl/tls.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
HSTS (Strict-Transport-Security: max-age=31536000; includeSubDomains) is added from the SSL template only. The HTTP-only template deliberately omits HSTS — setting it when TLS is terminated upstream could lock users out if the upstream terminator fails over to plain HTTP.
To change cipher suites or protocol versions, edit the template in docker/ops/ and rebuild the image. There are no runtime flags for this.
Environment variables
| Variable | Default | Purpose
| BACKEND_URL | http://ft-configs-service:8080 | proxy_pass target for the /configs-service/ and /ready locations.
| METRICS_ALLOW_IP | 0.0.0.0/0 | Extra CIDR/IP whitelisted for /nginx_status on top of the built-in 127.0.0.1, 10.0.0.0/8, 172.16.0.0/12.
See Configuration and Docker & Deploy for the complete env reference.
Certificate file permissions
The nginx:alpine image runs its worker processes as the nginx user, but the master process starts as root (default upstream behavior). Because the master reads the cert/key before dropping privileges, world-readable permissions are not required — owner-read is enough, and private keys should stay strict:
chmod 600 certs/tls.key
chmod 644 certs/tls.crt
Kubernetes
Mount a kubernetes.io/tls Secret directly:
apiVersion: v1
kind: Secret
metadata:
name: ft-configs-ui-tls
type: kubernetes.io/tls
data:
tls.crt: <base64>
tls.key: <base64>
---
spec:
containers:
- name: ft-configs-ui
image: ft-configs-ui:latest
volumeMounts:
- name: tls
mountPath: /etc/nginx/ssl
readOnly: true
volumes:
- name: tls
secret:
secretName: ft-configs-ui-tls
When the cluster uses Ingress-level TLS termination, omit the volume entirely and let the image fall through to HTTP-only.
Dockerfile reference
The production Dockerfile (docker/Dockerfile) already declares both ports:
EXPOSE 80 443
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
No extra steps are needed to "turn on" HTTPS at build time — the image supports both modes and auto-selects at startup.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Container runs but only port 80 responds; 443 refused |
|
Verify the volume mount path and file names. Check stdout for the |
|
File is missing, malformed, or not PEM |
|
|
Key does not match the certificate, or key is encrypted with a passphrase |
Compare moduli: |
Browser shows |
Self-signed cert (including output of |
Import the cert into the OS/browser trust store, or replace with a cert issued by a trusted CA. |
|
nginx cannot reach the backend at |
Exec into the container: |
|
Caller IP is not in the allowlist ( |
Set |