Installation & Deployment

Installing the Provision Portal UI — an Angular single-page application served by Nginx, which also reverse-proxies /prov-api/ to the Provision Portal backend.

1. Overview

This guide deploys one container: the Provision Portal UI. The image is built from docker/Dockerfile on top of nginx:alpine; it copies the pre-built Angular bundle (dist/provision-portal_angular/browser) to /usr/share/nginx/html/provision-portal and serves it. There is no Node.js in the runtime image. The deployment method is Docker (Compose or docker run) — this repository does not build a distribution archive.

The Provision Portal backend (the Spring Boot / Flowable service served at the /prov-portal/ context path) is assumed to be already deployed and reachable. It is not deployed by this guide. The browser never talks to the backend directly: it calls the same-origin path /prov-api/, and Nginx proxies that to the URL in FT_PROV_API_URL.

2. Prerequisites

2.1. Host Requirements

Component Minimum Recommended Notes

Docker Engine

19.03

verify with DevOps

docker/docker-compose.yml declares Compose file format 3.9, which requires Engine 19.03 or newer.

Docker Compose

v2 (docker compose)

v2

All commands in this guide use the Compose v2 CLI plugin syntax.

RAM

verify with DevOps

verify with DevOps

Not pinned in this repository — docker/docker-compose.yml sets no deploy.resources limits.

Disk

verify with DevOps

verify with DevOps

Sized by the nginx:alpine image plus the static Angular bundle.

Node.js is required only to produce the Angular build from source (see [build-from-source]). The runtime image is nginx:alpine and contains jq, bash and gettext — no Node.js.

2.2. Required External Dependencies

These services must be installed, running, and reachable from this host before the Provision Portal UI starts. None of them are deployed by this guide.

Component Minimum Version Why It Is Needed Port Required

Provision Portal backend

verify with the backend owner

Serves every API call the SPA makes (authentication, provisioning, device status, logs, user management) at the /prov-portal/ context path. Without it the UI loads but every request through /prov-api/ fails, and Nginx will not start at all if the hostname does not resolve.

Taken from FT_PROV_API_URL. This repository does not fix the backend port: docker/ops/entrypoint.sh falls back to http://prov-api:8880/prov-portal/, while docker/docker-compose.yml sets a hostname with no port (so 80). Verify the real port with FT DevOps.

Yes

Registry reachability is not a runtime dependency of this container and is therefore not listed above; it is covered by Registry Access and Registry Authentication.

The backend in turn reaches the Friendly TR-069 ACS and its database. Those are never contacted by the browser or by this container.

2.3. Supported Operating Systems

Deployment Operating system

Docker (Compose or docker run)

Linux (recommended), macOS, or Windows with WSL2

2.4. Registry Access

Network access to the hub.friendly-tech.com Docker registry (or offline image archives — see Offline Servers).

Read-only pull credentials for the registry are issued by the DevOps team; request them before starting. They are sufficient for installation and upgrades.

This repository does not ship a pre-configured deployment package. The compose.yml and the TLS certificate directory are created on the target host from the examples in this guide; docker/docker-compose.yml in this repository is a local build-and-run file, not the deployment descriptor.

3. Network Requirements

Destination Port Protocol Purpose

Provision Portal backend (host taken from FT_PROV_API_URL)

Whatever FT_PROV_API_URL contains — not fixed by this repository (entrypoint fallback 8880, docker/docker-compose.yml no port, i.e. 80); verify with FT DevOps

HTTP

Nginx proxy_pass for the /prov-api/ location — every API call from the browser.

hub.friendly-tech.com

443

HTTPS

Pulling the ui/provision-portal image. Not required on offline hosts.

3.1. Docker Networking

The container reaches the backend by whatever hostname FT_PROV_API_URL contains, resolved from inside the container:

  • Backend in Docker: put both containers on the same Docker network and use the backend’s service or container name as the hostname.

    docker network create prov-net

    Add --network prov-net to docker run, or a networks: section to compose.yml:

    services:
      provision-portal:
        image: hub.friendly-tech.com/ui/provision-portal:<tag>
        networks:
          - prov-net
    
    networks:
      prov-net:
        external: true
  • Backend on the host machine: use host.docker.internal (works on Docker Desktop; on Linux add extra_hosts: ["host.docker.internal:host-gateway"] or --add-host=host.docker.internal:host-gateway), or the host’s real IP address.

localhost and 127.0.0.1 never work here — inside the container they point at the container itself.

4. Registry Authentication

All Provision Portal UI images are pulled from hub.friendly-tech.com. Authenticate once per host before the first docker compose up.

docker login hub.friendly-tech.com

Enter the read-only pull credentials when prompted. Request the user name and password from the DevOps team — they are not published in this documentation.

The read-only account provides pull-only access to the published images. It cannot push. For elevated Harbor access (push rights, a dedicated robot token), contact the DevOps team.

Alternatively, log in non-interactively, reading the password from a file that is not committed anywhere:

docker login hub.friendly-tech.com -u <readonly-user> --password-stdin < ~/.ft-harbor-password

Verify authentication:

docker info | grep -A 5 Registry

Then pull the image:

docker pull hub.friendly-tech.com/ui/provision-portal:<tag>

CI publishes the image for linux/amd64 only (platforms: 'linux/amd64' in .github/workflows/build-callable.yml, and the same platform in docker/docker-push.sh).

Building the image from source. Developers who cannot use a published image build it locally. The Dockerfile is runtime-only: it copies an already-produced dist/, so the Angular build runs first.

git clone <repository-url> provision-portal_angular
cd provision-portal_angular

npm ci
npm run build            # emits dist/provision-portal_angular/browser

docker build -f docker/Dockerfile -t provision-portal-ui:local .

CI runs ng build --project=provision-portal_angular -c=production --base-href /provision-portal/ after npm install --legacy-peer-deps. The Node.js version is not pinned in this repository (no engines field, no setup-node step) — verify with the build owner before choosing one.

To build and push the way docker/docker-push.sh does:

docker buildx create --name multi --driver docker-container --use
docker buildx build --platform linux/amd64 --push \
  -f docker/Dockerfile -t hub.friendly-tech.com/ui/provision-portal:<tag> .

4.1. Offline Servers

When the Provision Portal UI host cannot reach hub.friendly-tech.com, pull the image on a machine that does have registry access, export it to an archive, transfer it, and load it on the offline host.

The connected machine can run Linux, macOS, or Windows — commands are given for both shells below. It does not need to be the same platform as the offline host.

An explicit --platform matching the offline host’s architecture is required. Without --platform, docker pull selects the host architecture, which may not match the target. On an Apple Silicon (arm64) Mac or an arm64 Windows machine without --platform, the resulting archive will be arm64 and will fail with a platform does not match warning on amd64 servers. The examples below use linux/amd64; step 1 shows how to read the correct value off the offline host. Only linux/amd64 is published by CI; do not rely on --platform linux/arm64 unless the release owner confirms an arm64 build exists.

  1. On the offline host, find out which architecture it runs — this is the value you will pass as PLATFORM below. Ask Docker itself, since it reports what the daemon will actually accept:

    docker version --format '{{.Server.Arch}}'

    If Docker is not installed there yet, use the operating system instead — uname -m on Linux, or echo $env:PROCESSOR_ARCHITECTURE in PowerShell on Windows. Map the result:

    docker version reports uname -m / Windows reports Use as PLATFORM

    amd64

    x86_64 / AMD64

    linux/amd64

    arm64

    aarch64 / ARM64

    linux/arm64

    On Windows with Docker Desktop, {{.Server.Arch}} reports the architecture of the Linux VM that actually runs the containers — which is the value you want, not the Windows host’s own architecture.

  2. On a machine with registry access, log in. Run this on its own — it prompts for a password, so anything pasted after it on the same go would be swallowed as input:

    docker login hub.friendly-tech.com
  3. Pull and export the image. Paste the whole block as-is; the only lines to change are PLATFORM and TAG on top.

    Linux / macOS (bash):

    PLATFORM=linux/amd64
    TAG=<tag>
    
    docker pull --platform "$PLATFORM" "hub.friendly-tech.com/ui/provision-portal:$TAG"
    docker save "hub.friendly-tech.com/ui/provision-portal:$TAG" | gzip > "provision-portal-ui-$TAG.tar.gz"

    Windows (PowerShell):

    $PLATFORM = "linux/amd64"
    $TAG = "<tag>"
    
    docker pull --platform $PLATFORM "hub.friendly-tech.com/ui/provision-portal:$TAG"
    docker save -o "provision-portal-ui-$TAG.tar" "hub.friendly-tech.com/ui/provision-portal:$TAG"

    On Windows, always write the archive with docker save -o <file>. Piping or redirecting docker save from PowerShell (docker save …​ > file.tar) corrupts the archive, because the PowerShell pipeline re-encodes the stream as text instead of passing raw bytes. docker load then fails with unexpected EOF or invalid tar header. To compress for transfer, use the bundled tar.exe (Windows 10 1803+ / Server 2019+): tar.exe -czf provision-portal-ui.tar.gz provision-portal-ui-$TAG.tar.

  4. Transfer the archive to the offline host, together with compose.yml, the shared .env, and the ssl/ directory holding friendly.crt and friendly.key — see Directory Layout.

  5. On the offline host, load the archive and start the service. Set TAG to the same value you used above:

    Linux (bash):

    TAG=<tag>
    
    gzip -dc "provision-portal-ui-$TAG.tar.gz" | docker load
    docker compose up -d provision-portal

    Windows (PowerShell):

    $TAG = "<tag>"
    
    docker load -i "provision-portal-ui-$TAG.tar"
    docker compose up -d provision-portal

Confirm the image is present before starting, so a missing or mis-architected image fails here rather than mid-startup:

docker images hub.friendly-tech.com/ui/provision-portal

Upgrades use the same flow: pull the new tag on the connected machine, transfer and load the archive, then docker compose up -d provision-portal.

This covers the Provision Portal UI image only. The Provision Portal backend is a separate image on its own host — for the offline procedure covering a whole stack, see All in one server deployment — Offline Servers, which derives the image list from compose.yml itself.

5. Preparation

5.1. Directory Structure

Create the deployment directory on the host:

mkdir -p /usr/local/ft-system/provision-portal/{ssl,nginx/logs}
cd /usr/local/ft-system

This follows the platform layout documented in Full System Deployment: one directory per service directly under /usr/local/ft-system.

5.1.1. Directory Layout

The Provision Portal UI slice of the platform layout — compose.yml and the shared .env at the root, everything owned by this service under provision-portal/:

/usr/local/ft-system/
├── compose.yml                             # from FT_DISK (or the example in <<docker-compose>>)
├── .env                                    # shared stack environment
└── provision-portal/
    ├── ssl/                                # -> /etc/nginx/ssl (read-only)
    │   ├── friendly.crt
    │   └── friendly.key
    └── nginx/
        └── logs/                           # -> /var/log/nginx (created automatically)
Path Content Backup

compose.yml

Stack definition — image tag, published ports, volumes and the env_file list for this service.

Yes

.env

Shared stack environment: FT_PROV_API_URL and the other inter-service URLs, database, Hazelcast and JWT values used by the whole stack. This service has no per-service .env in the platform layout.

Yes

provision-portal/ssl/

friendly.crt and friendly.key, mounted read-only at /etc/nginx/ssl. Both file names are fixed — docker/ops/nginx.conf.template references them literally, and Nginx does not start without them.

Yes

provision-portal/nginx/logs/

Nginx access and error logs, mounted at /var/log/nginx. Written by the container at runtime.

No

5.2. Environment Configuration

The platform uses a two-layer environment file architecture, and this service reads only the first layer:

  • Root .env — shared by every service: database connection (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD), Flowable DB, ClickHouse, PostgreSQL, Hazelcast, JWT, COMPOSE_PROFILES and the inter-service URLs. FT_PROV_API_URL, the only variable this container reads, belongs here.

  • <service>/.env — per-service host ports, pool tuning, JVM settings and feature flags. The platform compose.yml defines no provision-portal/.env: the UI’s published ports (PROV_PORTAL_HTTP_PORT, PROV_PORTAL_HTTPS_PORT) are documented under provision-api/.env. The Provision Portal backend guide lists a second env_file entry ./provision-portal/.env for this container; if you deploy from that guide’s compose.yml, create that file (it may be empty) or Compose aborts with env file ./provision-portal/.env not found. See docs/decisions/LOG.md.

There is no per-database-vendor env file. Selecting Oracle instead of the default MySQL means changing DB_PROFILE, SPRING_PROFILES_ACTIVE, DB_HOST and the other variables listed in Switching to Oracle inside the single root .env; compose.yml maps the generic DB_* names to the vendor-specific ones each application expects through the x-db-env / x-flowable-env YAML anchors. None of this reaches the Provision Portal UI container, which never talks to a database.

The variable read by this container:

Variable Description Default Required

FT_PROV_API_URL

Upstream URL used by Nginx proxy_pass for the /prov-api/ location. Point it at the backend’s /prov-portal/ context path. Read by docker/ops/entrypoint.sh and substituted into nginx.conf with envsubst.

http://prov-api:8880/prov-portal/

No

The browser’s API base path is not configurable. docker/ops/entrypoint.sh rewrites assets/config/app.config.json with jq so that .apiUrl is the fixed relative value /prov-api/, matching the Nginx location. The browser calls /prov-api/…​ same-origin, Nginx forwards to FT_PROV_API_URL.

The Angular app loads assets/config/app.config.json before it boots (ConfigService.loadConfig() in an app initializer), so apiUrl is available to the first HTTP call. See Configuration.

5.3. TLS Certificate (required)

There is no HTTPS_ENABLED switch and no keystore environment variable. nginx.conf.template contains listen 443 ssl unconditionally and reads /etc/nginx/ssl/friendly.crt and /etc/nginx/ssl/friendly.key, so in practice the certificate mount is mandatory: without it Nginx fails to start. TLSv1.2 and TLSv1.3 with ECDHE ciphers are configured in the template.

For local or internal use, generate a self-signed pair directly on the deployment host (run from /usr/local/ft-system, where the Directory Structure step left you). A deployment host installing from the registry image has no source checkout, so docker/generate-cert.sh is not available there; the command below is what that script does:

openssl req -x509 -nodes -days 3650 -newkey rsa:4096 \
  -keyout provision-portal/ssl/friendly.key \
  -out    provision-portal/ssl/friendly.crt \
  -subj "/C=IL/ST=Israel/L=Tel-Aviv/O=Friendly Tech./CN=friendly-tech.com" \
  -addext "subjectAltName=DNS:friendly-tech.com,DNS:*.friendly-tech.com,DNS:localhost,IP:127.0.0.1"
chmod 600 provision-portal/ssl/friendly.key
chmod 644 provision-portal/ssl/friendly.crt

In a source checkout the equivalent is cd docker && ./generate-cert.sh, which writes docker/ssl/friendly.crt and docker/ssl/friendly.key for the local build compose file.

For production, install a CA-issued pair under the same file names:

cp your-cert.crt  provision-portal/ssl/friendly.crt
cp your-cert.key  provision-portal/ssl/friendly.key
chmod 600 provision-portal/ssl/friendly.key

The certificate directory is mounted read-only at /etc/nginx/ssl and the HTTPS port is published by the compose.yml in Docker Compose (${PROV_PORTAL_HTTPS_PORT:-8893}:443).

Rotate certificates before expiry — the self-signed command above issues a 10-year certificate and is for internal use only. The container also serves plain HTTP on container port 80 (published as 8890); redirect or firewall it if your environment requires HTTPS only.

If Nginx fails to start because of the certificate, see Container Fails to Start.

6. Deployment

6.1. Startup Dependencies

  1. TLS certificate and key present in the mounted ssl directory — Nginx will not start without them.

  2. The hostname in FT_PROV_API_URL must resolve when the container starts: proxy_pass uses a literal host, which Nginx resolves while loading the configuration. The backend does not have to be serving requests yet, but its name must resolve.

  3. The image must be available locally (pulled or loaded).

6.2. Docker Compose

Create compose.yml in the deployment directory:

services:
  provision-portal:
    image: hub.friendly-tech.com/ui/provision-portal:<tag>
    env_file:
      - .env                     # shared stack environment; holds FT_PROV_API_URL
    ports:
      - "${PROV_PORTAL_HTTP_PORT:-8890}:80"    # HTTP  (host 8890 -> container 80)
      - "${PROV_PORTAL_HTTPS_PORT:-8893}:443"  # HTTPS (host 8893 -> container 443)
    volumes:
      - ./provision-portal/ssl:/etc/nginx/ssl:ro  # friendly.crt + friendly.key, required by the shipped config
      - ./provision-portal/nginx/logs:/var/log/nginx
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped

FT_PROV_API_URL is taken from the shared root .env. Do not add it under environment: in compose.yml — a compose environment: entry always wins over env_file, so it would pin one host for every deployment and, since Nginx resolves the proxy_pass host at startup (Startup Dependencies), the container would fail to start wherever that host does not resolve. Set the value once in .env:

FT_PROV_API_URL=http://provision-api:8080/prov-portal/

To change a published port, set PROV_PORTAL_HTTP_PORT / PROV_PORTAL_HTTPS_PORT in .env, or edit only the left side of a mapping (<host>:<container>).

docker compose up -d
docker compose ps

docker/docker-compose.yml in this repository is the local build variant: it builds from docker/Dockerfile, tags the result ui/provision-portal:tmp and publishes 8880:80 for local development only. The platform assigns this image the published ports 8890 → 80 and 8893 → 443 (Full System Deployment — Port Reference, PROV_PORTAL_HTTP_PORT / PROV_PORTAL_HTTPS_PORT in the environment reference); 8880 / 8843 on a shared host belong to the Portals container and would collide. This guide follows the platform — see docs/decisions/LOG.md.

Without Compose. The same deployment as a single docker run, executed from /usr/local/ft-system (where the Directory Structure step left you):

docker run -d \
  --name provision-portal-ui \
  -p 8890:80 \
  -p 8893:443 \
  -e FT_PROV_API_URL="http://provision-api:8080/prov-portal/" \
  -v "$PWD/provision-portal/ssl:/etc/nginx/ssl:ro" \
  -v "$PWD/provision-portal/nginx/logs:/var/log/nginx" \
  --add-host=host.docker.internal:host-gateway \
  --restart unless-stopped \
  hub.friendly-tech.com/ui/provision-portal:<tag>

7. Verification

7.1. Startup Log

docker compose logs -f provision-portal

docker/ops/entrypoint.sh prints, in order:

Generating nginx.conf from template...
nginx.conf generated with FT_PROV_API_URL=<your value>
Patched app.config.json -> .apiUrl = /prov-api/

A WARN: nginx.conf.template not found or WARN: …​ app.config.json not found (skipping) line means the image is not the expected one.

7.2. Endpoint Checks

# 1. Container is up with both mappings
docker compose ps

# 2. The SPA is served over HTTP (host 8890 -> container 80)
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8890/provision-portal/
# Expected: 200

# 3. The no-slash form redirects
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8890/provision-portal
# Expected: 301

# 4. TLS listener answers (host 8893 -> container 443)
curl -sk -o /dev/null -w "%{http_code}\n" https://localhost:8893/provision-portal/

# 5. The runtime API base was patched to the relative proxy path
docker compose exec provision-portal \
  cat /usr/share/nginx/html/provision-portal/assets/config/app.config.json
# Expected: { "apiUrl": "/prov-api/" }

# 6. The backend answers through the proxy
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8890/prov-api/

Then open http://<HOST>:8890/provision-portal/ in a browser.

8. Port Reference

Ports are written as published → container. Across hosts you connect to the published port; inside the Docker bridge you connect to the container port.

8.1. HTTP / HTTPS

Port Protocol Purpose Exposure

8890 → 80

HTTP

Serves the SPA under /provision-portal/ and the /prov-api/ reverse proxy. The container always listens on 80. Host port from PROV_PORTAL_HTTP_PORT.

Public

8893 → 443

HTTPS

TLS listener; requires friendly.crt + friendly.key under /etc/nginx/ssl/. The container always listens on 443. Host port from PROV_PORTAL_HTTPS_PORT.

Public

The published ports are the ones the platform assigns to this image; docker/docker-compose.yml in this repository publishes 8880:80 for local development only, and 8880 / 8843 belong to the Portals container on a shared host.

8.2. Outbound Connections

Destination Port Protocol Purpose

Provision Portal backend (host from FT_PROV_API_URL)

Whatever FT_PROV_API_URL contains — not fixed by this repository (entrypoint fallback 8880, docker/docker-compose.yml no port, i.e. 80); verify with FT DevOps

HTTP

proxy_pass target for /prov-api/, including WebSocket upgrade headers.

hub.friendly-tech.com

443

HTTPS

Image pull and update.

8.3. HTTP Endpoints

Method Path Purpose Auth

GET

/provision-portal

Redirects (301) to /provision-portal/.

None

GET

/provision-portal/

The SPA and its assets, with try_files fallback to index.html for deep links.

None

any

/prov-api/

Reverse-proxied to FT_PROV_API_URL.

Enforced by the backend

9. Stack Management

9.1. Logs

docker compose logs -f provision-portal
docker compose logs --tail 100 provision-portal

9.2. Start, Stop, Restart

docker compose up -d                       # start
docker compose stop provision-portal       # stop, keep the container
docker compose restart provision-portal    # restart (re-runs the entrypoint)
docker compose down                        # stop and remove

9.3. Shell Access

docker compose exec provision-portal bash          # bash is installed in the image
docker compose exec provision-portal cat /etc/nginx/nginx.conf
docker compose exec provision-portal env | grep FT_PROV_API_URL

9.4. Updating Provision Portal UI

Online hosts:

docker compose pull provision-portal
docker compose up -d provision-portal
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8890/provision-portal/

Offline hosts: pull, save and transfer the new image as described in Offline Servers, then

gzip -dc provision-portal-ui-<new-tag>.tar.gz | docker load
docker compose up -d provision-portal     # after updating the tag in compose.yml

To roll back, set the previous tag in compose.yml and run docker compose up -d provision-portal again.

10. Production Checklist

  • FT_PROV_API_URL points at the correct backend /prov-portal/ context path and its hostname resolves from inside the container.

  • A CA-issued friendly.crt + friendly.key are mounted at /etc/nginx/ssl/, and provision-portal/ssl/friendly.key is chmod 600.

  • Both mappings are published: 8890:80 and 8893:443.

  • The image tag is pinned to a release tag, never latest.

  • restart: unless-stopped is set.

  • Resource limits are set (deploy.resources.limits or --memory) — nothing is set by default.

  • The UI and the backend share a dedicated Docker network, or the backend host is reachable by name.

  • Container stdout/stderr is collected by the Docker logging driver or a log aggregator.

11. Troubleshooting

Start with:

docker compose logs --tail 100 provision-portal
docker compose exec provision-portal env | grep FT_PROV_API_URL

11.1. Container Fails to Start

Symptom: the container starts and immediately exits, or the log shows an SSL certificate error or an Nginx host not found in upstream error.

Cause and fix:

  • The certificate mount is missing. nginx.conf.template always listens on 443 and reads /etc/nginx/ssl/friendly.crt + friendly.key. Confirm the ./provision-portal/ssl volume is mounted and both files exist and are readable. For testing, generate a self-signed pair as shown in TLS Certificate (required).

  • The hostname in FT_PROV_API_URL does not resolve. Nginx resolves the literal proxy_pass host when it loads the configuration. Check the rendered file: docker compose exec provision-portal cat /etc/nginx/nginx.conf.

11.2. UI Loads but Requests Fail

Symptom: the login page appears, but login fails or no data loads.

Check:

docker compose exec provision-portal \
  cat /usr/share/nginx/html/provision-portal/assets/config/app.config.json
# Expected: { "apiUrl": "/prov-api/" }

curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8890/prov-api/

Fix:

  • Verify FT_PROV_API_URL points at a running backend and ends with the /prov-portal/ context path.

  • If the backend runs on the host, use host.docker.internal (with extra_hosts on Linux) or the host IP — never localhost.

  • If both run in Docker, put them on the same network and use the backend’s container name.

11.3. Port Already in Use

Symptom: address already in use when starting the container.

Check: lsof -i :8890 or docker ps.

Fix: stop the conflicting process, or change only the published side of the mapping (8080:80 instead of 8890:80).

11.4. Blank Page or JavaScript Errors

Symptom: the page loads blank, or the browser console shows errors.

Check: open DevTools → Console, and confirm assets/config/app.config.json is served and contains "apiUrl": "/prov-api/".

Fix: an empty or missing config file means the entrypoint’s jq patch did not run — check docker compose logs provision-portal for the Patched app.config.json line.

[[404-on-deep-links-or-refresh]] === 404 on Deep Links or Refresh

Symptom: opening a route directly, or refreshing, returns 404.

Fix: the location /provision-portal/ block uses try_files $uri $uri/ /provision-portal/index.html. Confirm the request path is under /provision-portal/ and that the bundle was copied to /usr/share/nginx/html/provision-portal (docker compose exec provision-portal ls /usr/share/nginx/html/provision-portal). The CI build sets --base-href /provision-portal/; a bundle built without it will not resolve its assets.

11.5. Getting Support

Collect the following before contacting the Friendly Technologies support team:

docker compose logs --tail 500 provision-portal > provision-portal-ui.log
docker compose exec provision-portal cat /etc/nginx/nginx.conf > nginx.rendered.conf
docker compose exec provision-portal \
  cat /usr/share/nginx/html/provision-portal/assets/config/app.config.json
docker inspect $(docker compose ps -q provision-portal) --format '{{json .Config.Env}}'

Include the image tag in use and whether the host is online or offline.

The repository README covers the local development workspace and the local production build.