docs(docker): restore self-hosted PWA compose flow

This commit is contained in:
4gray committed 2026-05-20 15:54:15 +02:00
1 parent 3096115e64
commit 23a1330a14
7 files changed
+377 -51

No files matched your search

+25 -17
View File
@@ -1,32 +1,40 @@
# Stage 1 - build environment
FROM node:22-alpine AS build
RUN apk add --no-cache python3 make g++ git
# Create app directory
WORKDIR /usr/src/app
# Swtich to node user
#RUN chown node:node ./
#USER node
COPY .npmrc ./
COPY package.json pnpm-lock.yaml ./
COPY patches ./patches
# Install app dependencies
RUN corepack enable && pnpm install --frozen-lockfile
RUN corepack enable && pnpm install --frozen-lockfile --ignore-scripts
# Copy all required files
COPY . .
# Build the application
RUN pnpm run build:web
RUN pnpm nx build web --configuration=pwa
RUN pnpm nx build web-backend
# Stage 2 - the production environment
FROM nginx:stable-alpine
FROM node:22-alpine
# Copy artifacts and nignx.conf
COPY --from=build /usr/src/app/dist/browser /usr/share/nginx/html
COPY --from=build /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN apk add --no-cache gettext nginx
CMD sed -i "s#http://localhost:3333#$BACKEND_URL#g" /usr/share/nginx/html/main.js && nginx -g 'daemon off;'
WORKDIR /opt/iptvnator
ENV PORT=3000
ENV BACKEND_URL=/api
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.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"]
+79 -11
View File
@@ -1,20 +1,88 @@
# Self-hosted version of IPTVnator
# Self-hosted IPTVnator
You can deploy and run the PWA version of IPTVnator on your own machine with `docker-compose` using the following command:
The self-hosted image contains both pieces required for the browser PWA:
$ cd docker
$ docker-compose up -d
- Angular PWA static files served by nginx
- The monorepo `web-backend` Express app proxied under `/api`
This command will launch the frontend and backend applications. By default, the application will be available at: http://localhost:4333/. The ports can be configured in the `docker-compose.yml` file.
The historical standalone `4gray/iptvnator-backend` image is no longer needed
for the default Docker deployment.
The web backend proxy accepts only `http` and `https` provider URLs. The PWA first registers provider URLs through `/provider-targets`, then uses the returned `targetId` for playlist, Xtream, and Stalker proxy calls. The backend blocks loopback, private, link-local, and reserved network targets by default to avoid exposing the self-hosted server as a generic internal-network fetcher. If you intentionally need to test against local mock servers or LAN-only IPTV sources, set `IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS=1` on the backend container and avoid exposing that instance to untrusted users.
## Run With Docker Compose
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`.
From the repository root:
## Build frontend
```bash
docker compose -f docker/docker-compose.yml up --build -d
```
$ docker build -t 4gray/iptvnator -f docker/Dockerfile .
The ready-to-run compose file is [`docker-compose.yml`](./docker-compose.yml).
By default the app is available at <http://localhost:4333>. No additional
environment variables, backend repository checkout, or separate backend
container are required for the default local deployment.
## Build backend
## Build The Image
You can find the backend app with all instructions in a separate GitHub repository - https://github.com/4gray/iptvnator-backend
```bash
docker build -t 4gray/iptvnator -f docker/Dockerfile .
```
The image build runs:
```bash
pnpm nx build web --configuration=pwa
pnpm nx build web-backend
```
## Runtime Configuration
The container writes `/usr/share/nginx/html/assets/app-config.js` on startup.
That file sets `window.__IPTVNATOR_CONFIG__.BACKEND_URL`, which the PWA reads
before it creates `PwaService`.
These variables are supported by the Docker image. The compose file sets the
safe local defaults shown below.
| Variable | Default | Purpose |
| ---------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `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. Multiple origins can be comma-separated. |
| `PORT` | `3000` | Internal Express backend port. nginx proxy config is rendered from the template to match it at startup. |
| `IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS` | `0` | Set to `1` or `true` only for trusted local/LAN deployments that intentionally proxy private network IPTV or mock endpoints. |
| `NODE_EXTRA_CA_CERTS` | unset | Optional Node.js CA bundle path for providers using private certificate authorities. Mount the CA file into the container and set this path. |
The web backend proxy accepts only `http` and `https` provider URLs. The PWA
first registers provider URLs through `/provider-targets`, then uses the
returned `targetId` for playlist, Xtream, and Stalker proxy calls. The backend
blocks loopback, private, link-local, and reserved network targets by default so
a publicly exposed instance cannot be used as a generic internal-network
fetcher. If you enable `IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS=1`, keep the
instance restricted to trusted users.
For providers that use private certificate authorities, keep TLS validation
enabled and pass the CA bundle to Node:
```yaml
services:
iptvnator:
volumes:
- ./ca.pem:/etc/ssl/private/provider-ca.pem:ro
environment:
NODE_EXTRA_CA_CERTS: /etc/ssl/private/provider-ca.pem
```
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
```bash
pnpm nx test web-backend
pnpm nx build web --configuration=pwa --skip-nx-cache
pnpm nx build web-backend
pnpm nx run web-e2e:e2e -- --project=chromium --grep @self-hosted
docker compose -f docker/docker-compose.yml config
```
+14 -12
View File
@@ -1,18 +1,20 @@
---
services:
backend:
image: 4gray/iptvnator-backend:latest
ports:
- "7333:3000"
environment:
- CLIENT_URL=http://localhost:4333
# this one should match with the address and port in frontend CLIENT_URL env
frontend:
iptvnator:
image: 4gray/iptvnator:latest
build:
context: ..
dockerfile: docker/Dockerfile
ports:
- "4333:80"
environment:
- BACKEND_URL=http://localhost:7333
# this one should match with the address of the backend service
BACKEND_URL: /api
CLIENT_URL: http://localhost:4333
PORT: "3000"
IPTVNATOR_PROXY_ALLOW_PRIVATE_NETWORKS: "0"
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
+69
View File
@@ -0,0 +1,69 @@
#!/bin/sh
set -eu
export PORT="${PORT:-3000}"
export BACKEND_URL="${BACKEND_URL:-/api}"
envsubst '${PORT}' < /etc/nginx/http.d/default.conf.template > /etc/nginx/http.d/default.conf
node <<'NODE'
const fs = require('node:fs');
fs.writeFileSync(
'/usr/share/nginx/html/assets/app-config.js',
`window.__IPTVNATOR_CONFIG__ = ${JSON.stringify(
{ BACKEND_URL: process.env.BACKEND_URL || '/api' },
null,
2
)};\n`
);
NODE
node /opt/iptvnator/web-backend/main.cjs &
BACKEND_PID=$!
cleanup_done=0
cleanup() {
if [ "$cleanup_done" -eq 1 ]; then
return
fi
cleanup_done=1
trap - INT TERM EXIT
if [ -n "${NGINX_PID:-}" ]; then
kill "$NGINX_PID" 2>/dev/null || true
fi
if [ -n "${BACKEND_PID:-}" ]; then
kill "$BACKEND_PID" 2>/dev/null || true
fi
}
trap cleanup INT TERM EXIT
backend_ready=0
for _ in $(seq 1 30); do
if wget -qO- "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
backend_ready=1
break
fi
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
wait "$BACKEND_PID"
exit $?
fi
sleep 1
done
if [ "$backend_ready" -ne 1 ]; then
echo "IPTVnator web backend did not become healthy on port ${PORT}."
exit 1
fi
nginx -g 'daemon off;' &
NGINX_PID=$!
wait "$NGINX_PID"
+25 -10
View File
@@ -1,11 +1,26 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
include /etc/nginx/extra-conf.d/*.conf;
}
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:${PORT}/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /assets/app-config.js {
add_header Cache-Control "no-store";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}