Mermaid flowchart covers: GoDaddy DDNS, pfSense NAT + split DNS, Let's Encrypt DNS-01, nginx SSL termination, Docker host-network containers, Proton Bridge SMTP relay, and Gitea git workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.7 KiB
4.7 KiB
www.kenjim.com
KenJim Technologies marketing site — React SPA with Express contact form backend, deployed on zet.home.arpa behind nginx SSL termination.
Deployment Architecture
flowchart TD
subgraph internet["Internet"]
browser(["Client browser"])
godaddy["GoDaddy DNS\nwww.kenjim.com CNAME → lair.kenjim.com\nlair.kenjim.com A → WAN IP (dynamic)"]
le["Let's Encrypt CA"]
end
subgraph pfsense["pfSense · home gateway"]
ddns["DDNS client\nupdates lair.kenjim.com A record\nwhen WAN IP changes"]
nat["NAT / Port Forward\nWAN :80 / :443 → zet :80 / :443"]
unbound["Unbound DNS Resolver\nwww / git / kenji.kenjim.com\n→ 172.27.0.35 (split DNS — bypasses NAT)"]
end
subgraph zet["zet.home.arpa · 172.27.0.35"]
subgraph ssl["SSL — acme.sh + Let's Encrypt"]
acme["acme.sh\nDNS-01 challenge via GoDaddy API"]
cert["/etc/nginx/ssl/kenjim.com/\nfullchain.pem · key.pem\nauto-renewed by cron"]
end
subgraph nginx_host["nginx · host process"]
nginxsrv[":80 → redirect HTTPS\n:443 · SSL termination\nproxy_pass → localhost:8080"]
end
subgraph docker["Docker · network_mode: host"]
fe["www-kenjim-frontend\nnginx :8080\nReact SPA (pre-built image)\n/api/* proxied → :3001"]
api["www-kenjim-api\nExpress.js :3001\nPOST /contact handler"]
end
bridge["Proton Mail Bridge\n127.0.0.1:1025 SMTP relay"]
gitea[("Gitea · git.kenjim.com\nkenjim/www.kenjim.com")]
end
protonmail["Proton Mail\ninfo@kenjim.com → kenji@kenjim.com"]
browser -->|"DNS lookup"| godaddy
browser -->|"HTTPS :443"| nat
nat -->|"port forward"| nginxsrv
unbound -.->|"LAN clients resolve direct"| nginxsrv
nginxsrv -->|"proxy_pass :8080"| fe
fe -->|"proxy /api/*"| api
api -->|"SMTP 127.0.0.1:1025"| bridge
bridge -->|"relay via Proton servers"| protonmail
acme <-->|"set DNS TXT record\nfor domain validation"| godaddy
acme <-->|"certificate issuance"| le
acme -->|"writes cert"| cert
cert -->|"loaded by"| nginxsrv
ddns -->|"keeps A record current"| godaddy
gitea -->|"git pull · docker compose up --build"| fe
gitea -->|"git pull · docker compose up --build"| api
Structure
www.kenjim.com/
├── docker-compose.yml # both services run network_mode: host
├── frontend/
│ ├── Dockerfile # node:20 build → nginx:alpine serve
│ ├── nginx.conf # :8080, SPA fallback, /api/ proxy → :3001
│ └── src/ # React source (Vite)
└── backend/
├── Dockerfile
└── server.js # Express, POST /contact → nodemailer
Development Workflow
Edit on Mac → push to Gitea → pull + rebuild on zet:
Mac Gitea (git.kenjim.com) zet.home.arpa
│ git push │ │
├──────────────────────────────►│ │
│ │ git pull │
│ ├─────────────────────────►│
│ │ │ docker compose up --build -d
Running on zet
cd ~/workspace/src/personal/www.kenjim.com
docker compose up --build -d # build images and start
docker compose down # stop
docker compose logs -f # watch logs
docker logs www-kenjim-api # backend only
docker logs www-kenjim-frontend # frontend only
Environment
.env lives on zet only (not in git). Required vars:
| Variable | Example | Notes |
|---|---|---|
SMTP_HOST |
127.0.0.1 |
Proton Bridge SMTP host |
SMTP_PORT |
1025 |
Proton Bridge SMTP port |
SMTP_SECURE |
false |
Bridge uses STARTTLS, not SSL-on-connect |
SMTP_USER |
kenji@kenjim.com |
Bridge auth credential |
SMTP_PASS |
<bridge-password> |
From Bridge SMTP settings |
FROM_EMAIL |
info@kenjim.com |
Sender address shown in emails |
CONTACT_TO |
info@kenjim.com |
Destination for contact form submissions |
SSL Certificate
Issued by Let's Encrypt via DNS-01 challenge using the GoDaddy API. Managed by acme.sh on zet:
# manual renewal (cron handles this automatically)
~/.acme.sh/acme.sh --renew -d kenjim.com --wildcard --dns dns_gd
Cert location: /etc/nginx/ssl/kenjim.com/
Mac Setup (first time)
git clone http://kenjim@git.kenjim.com/kenjim/www.kenjim.com.git
cd www.kenjim.com
# edit frontend/src, then:
git add . && git commit -m "..." && git push