Installation & Deployment
Overview
This guide walks you through installing and running FT Configs UI — a Vite + React single-page application for managing TR-069/CWMP device configuration. Docker is the recommended deployment method for consistency and ease of use.
The deployment includes:
-
FT Configs UI — static SPA served by
nginx:alpine, which also reverse-proxies/configs-service/to the backend -
Docker container — Pre-built image from Harbor registry or loaded from archive
FT Configs UI is a frontend-only application that requires an external backend service (ft-configs-service) for all data operations.
Prerequisites
System Requirements
-
Docker Engine 20.10+
-
Docker Compose 2.0+
-
Minimum 512 MB RAM (1+ GB recommended)
-
500 MB free disk space for the image
|
Node.js is only required for local development and for producing a build from source. The production runtime image is |
Required External Components
The following components must be installed and accessible before starting:
| Component | Purpose | Default Port |
|---|---|---|
ft-configs-service |
Backend API for authentication, configuration CRUD, import/export |
8080 |
Supported Operating Systems
-
Linux (recommended)
-
macOS
-
Windows with WSL2
Before starting, make sure:
-
Docker Engine 20.10+ and Docker Compose 2.0+ installed. If Docker is not installed, follow the Docker Installation Guide.
-
You have network access to
ft-configs-servicebackend.
Quick Start
For experienced users who already have Docker installed and the backend running.
# 1. Get the Docker image (choose one):
# Option A: Pull from Harbor registry (recommended)
# Read-only pull credentials:
# Username: readonly
# Password: fokxuw-fymte1-taSxyc
docker login hub.friendly-tech.com
docker pull hub.friendly-tech.com/configs/ft-configs-ui:latest
# Option B: Load from archive (for offline servers — see "Transfer Image to Offline Server" below)
# gzip -dc ft-configs-ui-<version>.tar.gz | docker load
# 2. Start the application
docker run --rm -p 80:80 \
-e BACKEND_URL="http://ft-configs-service:8080" \
hub.friendly-tech.com/configs/ft-configs-ui:latest
# 3. Verify
curl -s http://localhost/ | head -5
After startup the application is available at http://localhost/.
Preparation
1. Get the Docker Image
Option A: Pull from Harbor Registry (recommended)
# Authenticate with the registry.
# Read-only pull credentials:
# Username: readonly
# Password: fokxuw-fymte1-taSxyc
docker login hub.friendly-tech.com
# Pull the latest image (optional if using docker compose —
# "docker compose up" will pull the image automatically)
docker pull hub.friendly-tech.com/configs/ft-configs-ui:latest
To pin to a specific release version:
docker pull hub.friendly-tech.com/configs/ft-configs-ui:v1.0.0
|
The version |
Optionally, retag the image for shorter references in docker run commands:
docker tag hub.friendly-tech.com/configs/ft-configs-ui:latest ft-configs-ui:latest
If you skip retagging, use the full image name in all subsequent commands.
The readonly account above provides pull-only access and is sufficient for installation and upgrades.
To request elevated Harbor access (for example, push rights or a dedicated robot token), contact the DevOps team.
Option B: Transfer Image to Offline Server
Use this option when the target server has no internet access and cannot pull images from Harbor directly. The image is pulled on a machine that does have Harbor access, exported to a tar archive, transferred to the offline server, and loaded there.
-
On a machine with Harbor access, log in:
# Read-only pull credentials: # Username: readonly # Password: fokxuw-fymte1-taSxyc docker login hub.friendly-tech.com -
Pull the image for the target server’s architecture:
docker pull --platform linux/amd64 hub.friendly-tech.com/configs/ft-configs-ui:<version>An explicit
--platformmatching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64,linux/arm64); without--platform,docker pullselects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without--platform, the resulting archive will be arm64 and will fail with aplatform does not matchwarning on amd64 servers. The example useslinux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.). -
Save the image to a tar archive and compress it:
docker save hub.friendly-tech.com/configs/ft-configs-ui:<version> -o ft-configs-ui-<version>.tar gzip ft-configs-ui-<version>.tar -
Transfer
ft-configs-ui-<version>.tar.gzto the offline server (e.g., viascpor removable media). -
On the offline server, load the image:
gzip -dc ft-configs-ui-<version>.tar.gz | docker load
Option C: Build from Source (developers only)
End-to-end build (Node builder + nginx runner in one image):
git clone <repository-url> ft-configs-ui
cd ft-configs-ui
docker build -f docker/Dockerfile -t ft-configs-ui:local .
If you already have a pre-built dist/ (for example, produced in CI), use the thin runner image:
npm ci
npm run build
docker build \
-f docker/Dockerfile.simple \
--build-arg DIST_DIR=dist \
-t ft-configs-ui:local .
|
Building from source requires Node.js 20 to install dependencies. Loading a pre-built image is faster and avoids build-environment issues. |
After loading or pulling, verify the image is available:
docker images | grep ft-configs-ui
2. Configure Environment
FT Configs UI reads its configuration from environment variables passed to the container. The main knob is:
-
BACKEND_URL— used by nginxproxy_passfor the/configs-service/location. This is the real upstream the proxy talks to.
The browser’s API base path is not configurable: entrypoint.sh writes a fixed window.RUNTIME_CONFIG.API_URL = "/configs-service" into runtime-config.js, matching the nginx /configs-service/ location and the backend context-path. The browser calls the relative path /configs-service/…, nginx intercepts that and forwards it to BACKEND_URL.
| Variable | Description | Default | Required |
|---|---|---|---|
|
Upstream URL used by nginx |
No |
|
|
Extra CIDR added to the allow-list of |
|
No |
|
If the backend runs on the host machine (not in Docker), use one of the following as the hostname in
|
Deployment
Option 1: Docker Compose (recommended)
Create a compose.yml file:
services:
ft-configs-ui:
image: hub.friendly-tech.com/configs/ft-configs-ui:latest
# build:
# context: .
# dockerfile: docker/Dockerfile
container_name: ft-configs-ui
ports:
- "80:80" # HTTP
# - "443:443" # Uncomment when mounting TLS certs (see HTTPS Deployment)
environment:
BACKEND_URL: "http://ft-configs-service:8080"
# METRICS_ALLOW_IP: "10.0.0.0/8"
# volumes:
# - ./certs:/etc/nginx/ssl:ro # Mount tls.crt + tls.key to enable HTTPS
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:80/health"]
interval: 30s
timeout: 5s
start_period: 15s
retries: 3
To change the external port, edit only the left side of the mapping (<host-port>:<container-port>),
for example 8080:80 to serve on port 8080 externally.
# Start the application
docker compose up -d
# View logs
docker compose logs -f ft-configs-ui
# Stop
docker compose down
Option 2: docker run
docker run -d \
--name ft-configs-ui \
-p 80:80 \
-e BACKEND_URL="http://ft-configs-service:8080" \
--add-host=host.docker.internal:host-gateway \
--restart unless-stopped \
hub.friendly-tech.com/configs/ft-configs-ui:latest
If the default host port is busy, change only the left side:
-p 8080:80.
If you retagged the image locally, use ft-configs-ui:latest instead of the full Harbor path.
Docker Networking
If the UI container must communicate with the backend on the same Docker network:
docker network create configs-net
Add --network configs-net to your docker run command or add the network section to compose.yml.
Use the container name (e.g., ft-configs-service) as the hostname in BACKEND_URL.
# compose.yml (with shared network)
services:
ft-configs-ui:
image: hub.friendly-tech.com/configs/ft-configs-ui:latest
ports:
- "80:80"
environment:
BACKEND_URL: "http://ft-configs-service:8080"
networks:
- configs-net
restart: unless-stopped
networks:
configs-net:
external: true
Use the backend container name (e.g. ft-configs-service) inside BACKEND_URL. Nginx is configured with resolver 127.0.0.11 (Docker’s embedded DNS) so the upstream name is resolved at request time, which lets the UI container start even before the backend is ready.
HTTPS Deployment
TLS is enabled automatically when the container starts with certificate files mounted at a known path. There is no HTTPS_ENABLED switch.
On startup, docker/ops/entrypoint.sh looks for both files:
-
/etc/nginx/ssl/tls.crt -
/etc/nginx/ssl/tls.key
If both exist, it uses nginx.conf.template (listens on 80 and 443, TLSv1.2+1.3, ECDHE ciphers). If either is missing, it uses nginx-http-only.conf.template (listens on 80 only), which is the expected mode behind a Kubernetes Ingress where TLS is terminated upstream.
To enable HTTPS in Docker Compose:
-
Generate or obtain certificate files. For local testing,
docker/generate-cert.shcan produce a self-signed pair. -
Mount them read-only into
/etc/nginx/ssl/:volumes: - ./certs:/etc/nginx/ssl:ro ports: - "80:80" - "443:443"
For further details (certificate provisioning, Kubernetes Ingress integration, troubleshooting), see HTTPS Deployment Guide.
Verify Deployment
After starting the container, run the following checks:
# 1. Check container status
docker ps -f name=ft-configs-ui
# Expected: container with status "Up" and port 0.0.0.0:80->80/tcp
# 2. Check the liveness endpoint
curl -s -o /dev/null -w "%{http_code}" http://localhost/health
# Expected: 200
# 3. Check runtime configuration was injected
curl -s http://localhost/runtime-config.js
# Expected: window.__RUNTIME_CONFIG__ = {"API_URL":"/configs-service"};
# 4. Check readiness (proxied to backend /actuator/health)
curl -s -o /dev/null -w "%{http_code}" http://localhost/ready
# 5. Check application logs
docker logs ft-configs-ui --tail 20
# 6. Open in browser
# http://localhost/
|
If the UI loads but shows connection errors, verify that |
Runtime Configuration Injection
At container start, docker/ops/entrypoint.sh:
-
Writes
/usr/share/nginx/html/runtime-config.jswith a fixedwindow.RUNTIME_CONFIG = {"API_URL":"/configs-service"};(the path must match the nginx/configs-service/location and the backend context-path). -
Reads
BACKEND_URLandMETRICS_ALLOW_IPand exports them forenvsubst. -
Detects TLS certificates at
/etc/nginx/ssl/tls.crt+tls.key, then picksnginx.conf.template(HTTPS + HTTP) ornginx-http-only.conf.template(HTTP only). -
Renders the chosen template with
envsubstto/etc/nginx/nginx.confand execsnginx -g 'daemon off;'.
This mechanism allows the same Docker image to connect to different backends and to run with or without TLS without rebuilding.
index.html loads /runtime-config.js via a plain <script> synchronously before the app bundle, so window.RUNTIME_CONFIG.API_URL is available to the first module that imports src/lib/runtime-config.ts. The loaded object is validated against a Zod schema and rejected with a clear error if malformed.
Environment Variables (Complete Reference)
Runtime variables (container)
These are read by docker/ops/entrypoint.sh at container start.
| Variable | Description | Default | Required |
|---|---|---|---|
|
Upstream URL for nginx |
No |
|
|
Extra CIDR added to the allow-list of |
|
No |
Build-time variables (Vite)
These are consumed by Vite at build time via import.meta.env.VITE_* and injected into the bundle. They are defined in vite.config.ts and read from version.json.
| Variable | Description | Default |
|---|---|---|
|
Application version string shown in the UI. |
Read from |
|
Build identifier shown in the UI. |
Read from |
TLS (no environment variables)
There is no HTTPS_ENABLED switch. TLS is activated when both /etc/nginx/ssl/tls.crt and /etc/nginx/ssl/tls.key are present at container start — mount them via a volume or a Kubernetes secret. The nginx templates configure TLSv1.2+1.3 and ECDHE-based ciphers. See HTTPS Deployment Guide for details.
Container Management
| Command | Description |
|---|---|
|
Start the service in the background |
|
Stop and remove containers |
|
Follow application logs |
|
Show container status |
|
Restart the application |
|
Update images to latest versions |
|
View last 100 log lines |
|
View resource usage (CPU, memory) |
|
Check environment variables |
Updating
Via Docker Compose (recommended)
docker compose pull downloads the latest image from the registry, then docker compose up -d recreates the container with the new version.
# 1. Pull the latest image
docker compose pull ft-configs-ui
# 2. Recreate the container with the new image
docker compose up -d ft-configs-ui
# 3. Verify
curl -s -o /dev/null -w "%{http_code}" http://localhost/health
# Expected: 200
Via docker load (offline servers)
When the upgrade target server has no access to the Harbor registry, the new image must be pulled on a machine that does have Harbor access, exported to a tar archive, transferred to the offline server, and loaded there.
-
On a machine with Harbor access, log in:
# Read-only pull credentials: # Username: readonly # Password: fokxuw-fymte1-taSxyc docker login hub.friendly-tech.com -
Pull the new image for the target server’s architecture:
docker pull --platform linux/amd64 hub.friendly-tech.com/configs/ft-configs-ui:<new-version>An explicit
--platformmatching the offline target server’s architecture is required. The image in Harbor is multi-arch (linux/amd64,linux/arm64); without--platform,docker pullselects the host architecture, which may not match the target. For example, on an Apple Silicon (arm64) Mac without--platform, the resulting archive will be arm64 and will fail with aplatform does not matchwarning on amd64 servers. The example useslinux/amd64; replace it with the platform of your offline target server (linux/arm64, etc.). -
Save the image to a tar archive and compress it:
docker save hub.friendly-tech.com/configs/ft-configs-ui:<new-version> -o ft-configs-ui-<new-version>.tar gzip ft-configs-ui-<new-version>.tar -
Transfer
ft-configs-ui-<new-version>.tar.gzto the offline server (e.g., viascpor removable media). -
On the offline server, load the new image and replace the running container:
# 1. Load the new image from archive gzip -dc ft-configs-ui-<new-version>.tar.gz | docker load # 2. Replace the container docker stop ft-configs-ui docker rm ft-configs-ui docker run -d \ --name ft-configs-ui \ -p 80:80 \ -e BACKEND_URL="http://ft-configs-service:8080" \ --restart unless-stopped \ hub.friendly-tech.com/configs/ft-configs-ui:<new-version> # 3. Verify curl -s -o /dev/null -w "%{http_code}" http://localhost/health
Production Checklist
| # | Item | Notes |
|---|---|---|
1 |
Backend URL |
Verify |
2 |
Restart policy |
|
3 |
Health check |
Configured in Docker Compose example above |
4 |
HTTPS |
Mount TLS certificates into |
5 |
Metrics allow-list |
Restrict |
6 |
Resource limits |
Add |
7 |
Log management |
Collect container stdout/stderr via Docker logging driver or log aggregator |
8 |
Network isolation |
Place UI and backend on a dedicated Docker network |
Troubleshooting
Start with quick diagnostics:
docker logs ft-configs-ui --tail 100
docker inspect ft-configs-ui --format '{{json .Config.Env}}'
Common Issues
UI loads but shows "Network Error" or connection errors
Symptom: The login page appears, but login fails or data does not load.
Check:
# Verify runtime-config.js contains the correct URL
curl -s http://localhost/runtime-config.js
# Check if backend is reachable from inside the container
docker exec ft-configs-ui wget -qO- "$BACKEND_URL/actuator/health" 2>&1 || echo "Backend unreachable"
Solution:
-
Verify
BACKEND_URLis correct and the backend is running. -
If backend runs on host, use
host.docker.internalinstead oflocalhost. -
If using Docker networking, ensure both containers share the same network.
Container exits immediately
Symptom: Container starts and immediately stops.
Check:
docker logs ft-configs-ui
Solution:
-
Inspect the generated config:
docker exec ft-configs-ui cat /etc/nginx/nginx.conf. A syntax error in anenvsubst-rendered template will stop nginx on startup. -
If you mounted TLS certificates, verify the files exist at
/etc/nginx/ssl/tls.crtand/etc/nginx/ssl/tls.keyinside the container and that the mount is readable. When either is missing,entrypoint.shautomatically falls back to the HTTP-only template — a container that still exits usually means a brokenBACKEND_URLor malformed envsubst variable.
Port conflict
Symptom: address already in use error.
Check:
lsof -i :80
# or
docker ps
Solution:
-
Stop the conflicting service, or expose a different host port:
-p 8080:80.
Blank page or JavaScript errors
Symptom: Page loads but shows a blank screen or console errors.
Check:
-
Open browser DevTools → Console tab.
-
Look for
RUNTIME_CONFIGerrors. -
Check if
runtime-config.jsis loaded: view page source, search forruntime-config.
Solution:
-
Verify
/usr/share/nginx/html/runtime-config.jsis present inside the container:docker exec ft-configs-ui cat /usr/share/nginx/html/runtime-config.js.entrypoint.shregenerates it on every start, so an empty or missing file points at a failed entrypoint run — checkdocker logs. -
Rebuild the image from scratch:
docker build --no-cache -f docker/Dockerfile -t ft-configs-ui:local .
CORS errors
Symptom: Browser console shows Access-Control-Allow-Origin errors.
Solution:
-
In the recommended deployment the browser talks to nginx on the same origin as the UI and nginx proxies
/configs-service/to the backend, so there should be no cross-origin request in the first place. The browser always uses the fixed relative path/configs-service, so a CORS error usually points to nginx not proxying it — check the/configs-service/location andBACKEND_URL. -
If you really need cross-origin access, ensure
ft-configs-serviceCORS settings allow the UI origin and that credentials are permitted (the axios client useswithCredentials: true).
Login fails with 401 or CSRF errors
Symptom: Login form submits but returns 401 or 403.
Check:
-
Browser DevTools → Network tab → verify
/csrfcall succeeds before login POST. -
Verify
withCredentialscookies are being sent (check cookie headers).
Solution:
-
Ensure backend CSRF endpoint is accessible from the UI.
-
Verify backend allows cookies from the UI origin (
SameSite,Secureflags). -
Clear browser cookies and retry.
Port Reference
| Port | Protocol | Description |
|---|---|---|
80 |
HTTP |
nginx inside the production container (always listens) |
443 |
HTTPS |
nginx TLS listener — active only when |
9002 |
HTTP |
Vite dev server (local development only — see Local Development) |
When TLS certificates are mounted, the container listens on both port 80 and 443 simultaneously.
Publishing both Docker mappings (-p 80:80 and -p 443:443) is safe.
|