mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
fix(docker): wait for self-hosted backend startup
This commit is contained in:
1 parent
c3194db98e
commit
8f7d4a5f18
6 files changed
+77
-7
No files matched your search
+5
-2
@@ -17,7 +17,7 @@ RUN pnpm nx build web-backend
|
||||
|
||||
FROM node:22-alpine
|
||||
|
||||
RUN apk add --no-cache nginx
|
||||
RUN apk add --no-cache gettext nginx
|
||||
|
||||
WORKDIR /opt/iptvnator
|
||||
|
||||
@@ -27,11 +27,14 @@ ENV CLIENT_URL=http://localhost:4333
|
||||
|
||||
COPY --from=build /usr/src/app/dist/apps/web /usr/share/nginx/html
|
||||
COPY --from=build /usr/src/app/dist/apps/web-backend ./web-backend
|
||||
COPY docker/nginx.conf /etc/nginx/http.d/default.conf
|
||||
COPY docker/nginx.conf /etc/nginx/http.d/default.conf.template
|
||||
COPY docker/docker-entrypoint.sh /usr/local/bin/iptvnator-entrypoint
|
||||
|
||||
RUN chmod +x /usr/local/bin/iptvnator-entrypoint
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://127.0.0.1/api/health >/dev/null || exit 1
|
||||
|
||||
CMD ["iptvnator-entrypoint"]
|
||||
+6
-3
@@ -39,7 +39,7 @@ before it creates `PwaService`.
|
||||
| ---------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `BACKEND_URL` | `/api` | Browser-facing backend URL used by the PWA. Keep `/api` for the bundled nginx proxy. |
|
||||
| `CLIENT_URL` | `http://localhost:4333` | Allowed browser origin for backend CORS. Use the public URL when hosting behind a reverse proxy. |
|
||||
| `PORT` | `3000` | Internal Express backend port. nginx proxy config is patched to match it at container startup. |
|
||||
| `PORT` | `3000` | Internal Express backend port. nginx proxy config is rendered from the template to match it at startup. |
|
||||
| `IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS` | unset | Set to `1` only for trusted local/LAN deployments that intentionally proxy private network IPTV or mock endpoints. |
|
||||
|
||||
The web backend proxy accepts only `http` and `https` provider URLs. The PWA
|
||||
@@ -53,8 +53,11 @@ instance restricted to trusted users.
|
||||
For providers that use private certificate authorities, keep TLS validation
|
||||
enabled and pass the CA bundle to Node with `NODE_EXTRA_CA_CERTS=/path/to/ca.pem`.
|
||||
|
||||
The nginx config serves the PWA with SPA fallback, avoids caching
|
||||
`assets/app-config.js`, and proxies `/api/*` to the internal backend.
|
||||
The entrypoint renders the nginx config from `docker/nginx.conf`, starts the
|
||||
backend, waits for `/health`, and only then starts nginx. The nginx config
|
||||
serves the PWA with SPA fallback, avoids caching `assets/app-config.js`, and
|
||||
proxies `/api/*` to the internal backend. The Dockerfile and compose file both
|
||||
define a health check against `/api/health`.
|
||||
|
||||
## Local Validation
|
||||
|
||||
|
||||
@@ -10,3 +10,9 @@ services:
|
||||
environment:
|
||||
BACKEND_URL: /api
|
||||
CLIENT_URL: http://localhost:4333
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/api/health >/dev/null || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
@@ -7,7 +7,15 @@ set -eu
|
||||
|
||||
export PORT BACKEND_URL CLIENT_URL
|
||||
|
||||
sed -i "s#127.0.0.1:3000#127.0.0.1:${PORT}#g" /etc/nginx/http.d/default.conf
|
||||
nginx_template="/etc/nginx/http.d/default.conf.template"
|
||||
nginx_config="/etc/nginx/http.d/default.conf"
|
||||
|
||||
if ! grep -q '\${PORT}' "$nginx_template"; then
|
||||
echo "nginx template is missing the \${PORT} backend placeholder" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
envsubst '${PORT}' < "$nginx_template" > "$nginx_config"
|
||||
|
||||
node <<'NODE'
|
||||
const fs = require('node:fs');
|
||||
@@ -31,4 +39,52 @@ shutdown() {
|
||||
|
||||
trap shutdown INT TERM EXIT
|
||||
|
||||
node <<'NODE'
|
||||
const http = require('node:http');
|
||||
|
||||
const port = Number.parseInt(process.env.PORT || '3000', 10);
|
||||
const deadline = Date.now() + 30_000;
|
||||
const retryDelayMs = 250;
|
||||
|
||||
function retry(error) {
|
||||
if (Date.now() >= deadline) {
|
||||
const reason = error?.message ? `: ${error.message}` : '';
|
||||
console.error(`web-backend did not become ready on 127.0.0.1:${port}${reason}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
setTimeout(check, retryDelayMs);
|
||||
}
|
||||
|
||||
function check() {
|
||||
const request = http.get(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
path: '/health',
|
||||
port,
|
||||
timeout: 1000,
|
||||
},
|
||||
(response) => {
|
||||
response.resume();
|
||||
if (
|
||||
response.statusCode &&
|
||||
response.statusCode >= 200 &&
|
||||
response.statusCode < 500
|
||||
) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
retry(new Error(`HTTP ${response.statusCode}`));
|
||||
}
|
||||
);
|
||||
|
||||
request.on('timeout', () => {
|
||||
request.destroy(new Error('timeout'));
|
||||
});
|
||||
request.on('error', retry);
|
||||
}
|
||||
|
||||
check();
|
||||
NODE
|
||||
|
||||
nginx -g 'daemon off;'
|
||||
+1
-1
@@ -24,7 +24,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_pass http://127.0.0.1:${PORT}/;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
||||
@@ -106,6 +106,8 @@ The Docker image has two stages:
|
||||
1. Build stage installs dependencies and runs `web:pwa` plus `web-backend`.
|
||||
2. Runtime stage uses `node:22-alpine` with nginx installed. nginx serves
|
||||
`dist/apps/web` and proxies `/api/*` to the local Express backend.
|
||||
The entrypoint renders the nginx config from a `${PORT}` template, starts the
|
||||
backend, waits for `/health`, and then starts nginx.
|
||||
|
||||
Default runtime values:
|
||||
|
||||
|
||||
Reference in new issue
Block a user