Dockerize the proxy (all-in-one image) + Docker docs
All-in-one Dockerfile bundling OpenResty + the Node mgmt app + Redis in one container, mirroring the bare-metal ops/install.sh layout: - Dockerfile (openresty/openresty:1.31.1.1-2-bookworm-fat base; dumb-init PID 1; luarocks install lua-resty-auto-ssl/luasocket/lua-resty-ipmatcher; node 22.x; npm ci --omit=dev; OpenResty confs + lua copied into place). - docker-entrypoint.sh: fallback cert, sed-parameterize RESOLVER/REAL_IP_FROM, start bundled redis + node app, exec openresty foreground. - docker-compose.yml (standalone), .dockerignore, DEPLOYMENT.md. - nodejs/routes/render.js: /health endpoint for healthchecks. - nodejs/models/user_ldap.js: tlsOptions forwarded to ldapts Client so the proxy can bind ldaps:// with a self-signed cert (app_ldap__tlsOptions__*). - nodejs/package.json: bump @simpleworkjs/conf to ^1.1.0 (app_* env overrides). - docs/docker.md + index.md: Docker deployment guide + fronting an SSO Manager. - ops/proxy.service: add WorkingDirectory=/var/www/proxy/nodejs (bare-metal cwd fix so relative conf/ paths resolve). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# docker-entrypoint.sh — start the theta42/proxy all-in-one container.
|
||||
#
|
||||
# Bundles three processes (mirrors the bare-metal ops/install.sh layout):
|
||||
# 1. Redis (127.0.0.1:6379) — shared by the node app (model-redis),
|
||||
# lua-resty-auto-ssl cert storage, and targetinfo.lua.
|
||||
# 2. Node mgmt app (port 3000 + Unix socket /var/run/proxy_lookup.socket)
|
||||
# — started in the background.
|
||||
# 3. OpenResty (80/443/4443) — exec'd in the foreground as PID 2 (under
|
||||
# dumb-init, PID 1) so it receives SIGTERM from `docker stop`.
|
||||
#
|
||||
# The app reads its config from conf/base.js deep-merged with `app_*` env vars
|
||||
# (requires @simpleworkjs/conf >= 1.1.0, pinned in nodejs/package-lock.json). No
|
||||
# secrets.js is baked into the image — supply oidc/ldap/auth config via `app_*`
|
||||
# env (compose `environment:` / `env_file:`), or mount a conf/secrets.js.
|
||||
#
|
||||
# OpenResty config: the committed ops/nginx_conf/*.conf carry the bare-metal
|
||||
# home-LAN values (set_real_ip_from 192.168.1.0/24; resolver 192.168.1.1). They
|
||||
# are copied into the image at build time; this entrypoint sed-substitutes the
|
||||
# two env-specific values at runtime so the same committed files keep working
|
||||
# for bare metal (untouched) and Docker (parameterized).
|
||||
|
||||
set -e
|
||||
|
||||
info() { echo "[INFO] $*"; }
|
||||
error() { echo "[ERROR] $*" >&2; }
|
||||
|
||||
# ── Fallback SSL cert for lua-resty-auto-ssl ─────────────────────────────────
|
||||
# autossl.conf references /etc/ssl/resty-auto-ssl-fallback.{crt,key} — auto-ssl
|
||||
# serves this for unknown SNI before a real Let's Encrypt cert is issued.
|
||||
# Generate on first start (idempotent); mount your own to override.
|
||||
FALLBACK_CRT=/etc/ssl/resty-auto-ssl-fallback.crt
|
||||
FALLBACK_KEY=/etc/ssl/resty-auto-ssl-fallback.key
|
||||
if [[ ! -f "$FALLBACK_CRT" || ! -f "$FALLBACK_KEY" ]]; then
|
||||
info "Generating fallback SSL cert for auto-ssl..."
|
||||
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
|
||||
-subj '/CN=sni-support-required-for-valid-ssl' \
|
||||
-keyout "$FALLBACK_KEY" -out "$FALLBACK_CRT" >/dev/null 2>&1 || {
|
||||
error "Failed to generate fallback SSL cert"; exit 1
|
||||
}
|
||||
else
|
||||
info "Fallback SSL cert already present"
|
||||
fi
|
||||
|
||||
# ── Parameterize env-specific OpenResty directives ───────────────────────────
|
||||
# The committed confs hardcode the bare-metal home-LAN values. In Docker the
|
||||
# proxy is the front (no upstream proxy setting X-Real-IP) and upstream names
|
||||
# resolve via Docker's embedded DNS.
|
||||
NGINX_CONF=/usr/local/openresty/nginx/conf/nginx.conf
|
||||
PROXY_CONF=/usr/local/openresty/nginx/conf/sites-enabled/000-proxy
|
||||
|
||||
# RESOLVER: used by proxy_pass for upstream names recorded in Host records.
|
||||
# Default 127.0.0.11 = Docker's embedded DNS. Set RESOLVER to use a different
|
||||
# resolver (e.g. 8.8.8.8 for standalone, or your LAN DNS).
|
||||
RESOLVER="${RESOLVER:-127.0.0.11}"
|
||||
sed -i "s|resolver 8\.8\.4\.4 8\.8\.8\.8;|resolver ${RESOLVER};|" "$NGINX_CONF"
|
||||
sed -i "s|resolver 192\.168\.1\.1 ipv6=off;|resolver ${RESOLVER} ipv6=off;|" "$PROXY_CONF"
|
||||
|
||||
# REAL_IP_FROM: trusted source range for the X-Real-IP header. Empty (default)
|
||||
# removes the real_ip block entirely — correct when the proxy is the front
|
||||
# (clients connect directly, $remote_addr is already the real client, and no
|
||||
# one is trusted to forge X-Real-IP). Set REAL_IP_FROM to an upstream proxy's IP
|
||||
# range if something sits in front of this proxy and sets X-Real-IP.
|
||||
if [[ -z "${REAL_IP_FROM:-}" ]]; then
|
||||
info "REAL_IP_FROM unset — proxy is the front; removing real_ip block"
|
||||
sed -i '/set_real_ip_from\|real_ip_header\|real_ip_recursive/d' "$PROXY_CONF"
|
||||
else
|
||||
info "REAL_IP_FROM=${REAL_IP_FROM} — trusting X-Real-IP from that range"
|
||||
sed -i "s|set_real_ip_from 192\.168\.1\.0/24;|set_real_ip_from ${REAL_IP_FROM};|" "$PROXY_CONF"
|
||||
fi
|
||||
|
||||
# Validate the OpenResty config before starting anything else.
|
||||
if ! openresty -t >/dev/null 2>&1; then
|
||||
error "OpenResty config test failed:"
|
||||
openresty -t || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Redis ───────────────────────────────────────────────────────────────────
|
||||
# In-memory, no persistence (cache/session/cert data only). The app,
|
||||
# auto-ssl, and targetinfo.lua all reach it at 127.0.0.1:6379 (the redis client
|
||||
# default + the one literal in targetinfo.lua). Run in the background
|
||||
# (--daemonize no, backgrounded by the shell); the container's lifecycle is
|
||||
# owned by OpenResty (exec'd below), and `restart: unless-stopped` in compose
|
||||
# handles full restarts.
|
||||
info "Starting Redis..."
|
||||
redis-server --save "" --appendonly no --daemonize no &
|
||||
REDIS_PID=$!
|
||||
for i in $(seq 1 15); do
|
||||
if redis-cli ping >/dev/null 2>&1; then info "Redis is ready"; break; fi
|
||||
sleep 0.5
|
||||
done
|
||||
if ! redis-cli ping >/dev/null 2>&1; then
|
||||
error "Redis failed to start"; exit 1
|
||||
fi
|
||||
|
||||
# ── Node management app ──────────────────────────────────────────────────────
|
||||
# Matches ops/proxy.service: NODE_ENV=production, runs as root (the Unix socket
|
||||
# is chmod'd 777 by the app so OpenResty's nobody workers can connect).
|
||||
export NODE_ENV="${NODE_ENV:-production}"
|
||||
export NODE_PORT="${NODE_PORT:-3000}"
|
||||
info "Starting proxy management app on port ${NODE_PORT}..."
|
||||
node bin/www &
|
||||
NODE_PID=$!
|
||||
|
||||
# Give the app a moment to bind the socket + port before OpenResty fields
|
||||
# requests that may fall back to the socket for host lookups.
|
||||
for i in $(seq 1 20); do
|
||||
if curl -fsS http://localhost:${NODE_PORT}/health >/dev/null 2>&1; then
|
||||
info "Management app is ready"
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# ── OpenResty (foreground, primary process) ──────────────────────────────────
|
||||
info "Starting OpenResty..."
|
||||
# Replace the shell with openresty in the foreground so dumb-init forwards
|
||||
# SIGTERM to it. When OpenResty exits the container stops, taking the
|
||||
# backgrounded redis + node with it; `restart: unless-stopped` in compose
|
||||
# handles full restarts.
|
||||
exec openresty -g 'daemon off;'
|
||||
Reference in New Issue
Block a user