Production-ready sample showing:
- Frontend (ecochmanagerui) served by nginx
- Backend (ecochmanagerserver) Node/Express on port 3000
- Both services join nginx-proxy-manager_default network
- Clean CORS config for HTTPS
- Health checks and a minimal CI-friendly structure
Use this as a portfolio sample and a starting template for ecoachvms.com + api.ecoachvms.com.
- Nginx Proxy Manager (external) terminates SSL and forwards to services by container name on the shared Docker network.
- Frontend (
ecochmanagerui) → static files (nginx) on port 80. - Backend (
ecochmanagerserver) → Node/Express API on port 3000.
Browser (HTTPS)
│
▼
NPM (Let's Encrypt SSL)
├─ ecoachvms.com → http://ecochmanagerui:80
└─ api.ecoachvms.com → http://ecochmanagerserver:3000
This runs both services locally (without NPM).
cp server/.env.example server/.env
docker compose up --build -d
# Frontend: http://localhost:8080
# Backend health: http://localhost:3000/health
# Test API: curl http://localhost:3000/api/helloStop / clean
docker compose down -vBoth services must be attached to nginx-proxy-manager_default (or your NPM network name). This compose uses:
networks:
npm:
external: true
name: nginx-proxy-manager_defaultEnsure that network exists on your host (created by your NPM stack).
A) ecoachvms.com → Frontend
- Scheme:
http - Forward Hostname:
ecochmanagerui - Forward Port:
80 - SSL: Request Let's Encrypt, Force SSL, HTTP/2
B) api.ecoachvms.com → Backend
- Scheme:
http - Forward Hostname:
ecochmanagerserver - Forward Port:
3000 - SSL: Request Let's Encrypt, Force SSL, HTTP/2
add_header Access-Control-Allow-Origin "https://ecoachvms.com" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
if ($request_method = OPTIONS) { return 204; }
Prefer setting CORS in the backend (see
server/src/cors.js).
- Whitelist only production origins:
https://ecoachvms.comhttps://www.ecoachvms.com(if used)
Change allowed origins in server/.env → ALLOWED_ORIGINS (comma-separated).
- Frontend (served) → top-level
index.htmlloads. - Backend endpoint →
GET /healthreturns{ ok: true }
- 502/504 in NPM: services not on NPM network or wrong Forward Hostname.
- Backend reachable via curl but not via NPM: backend listening on
127.0.0.1only; set to0.0.0.0. - CORS errors: update
ALLOWED_ORIGINSor add NPM Advanced headers temporarily. - SSL fails: confirm DNS points to droplet IP; 80/443 open in firewall; re-issue cert in NPM.
docker-npm-worksample/
├─ docker-compose.yml
├─ README.md
├─ ui/
│ ├─ Dockerfile
│ └─ public/
│ └─ index.html
└─ server/
├─ Dockerfile
├─ .env.example
└─ src/
├─ cors.js
└─ index.js