GeoPulse 2.0 is a big release, and most of the work is under the hood. GeoPulse grew very quickly, and after many fast iterations a lot of internal shortcuts had piled up. This release cleans them up: the REST API was rebuilt, CSS styles were reorganized, error handling and logging were reworked, and the test suite was expanded with REST API integration tests. You will also see the results in the app: almost every page has been refreshed, GeoPulse can now be used in Ukrainian, and many new features were added along the way.
Upgrading is straightforward. Existing devices, healthchecks and Kubernetes probes keep working. Everyone signs in once after the upgrade, because sign-in sessions moved to the server. See Upgrading below for a few recommended updates.
Caution
Read Upgrading before you update. In short:
- Docker Compose / Unraid: change only
GEOPULSE_VERSIONand keep your current compose file. The new compose files use PostgreSQL 18, so switch to them only after you upgrade the database. - Kubernetes / Helm: the chart now defaults to PostgreSQL 18. Add
postgres.image.tag: "17-3.5"to your values beforehelm upgrade, or the database does not start. - Manual and Proxmox LXC: add one
locationblock to your nginx configuration, or the vector and 3D maps do not load.
Contents
- Highlights
- New features
- Upgrading: Docker Compose and Unraid · Kubernetes (Helm) · Manual installation and Proxmox LXC · PostgreSQL 18 · Recommended for everyone
- New configuration · Removed configuration
- Fixes
- Under the hood
Highlights
Localization
GeoPulse now has a localization framework. English and Ukrainian are supported, and more languages are coming. Each user can choose their language in their profile or from the avatar menu.
Refreshed UI on almost every page
Almost every page got visual and usability improvements. These pages were reworked more deeply:
- Profile and Timeline Preferences: reorganized into compact sections that are easier to review and edit
- Admin Settings and Admin Dashboard
- Rewind and Journey Insights
- Place, City and Country details and Coverage Explorer
- Trip Plans and Trip Workspace: Trip Workspace was fully reimplemented, and Trip Plans was improved on mobile
- Timeline map
- Navigation: see New navigation below
New navigation
The side menu had grown long, so account and personal settings moved to a new menu behind your avatar in the top bar. The side menu now holds only the places you work in. After the upgrade, a short hint points to the avatar once; it disappears after you click Got it or open the menu.
Avatar menu (top right):
- Your name and email, with an Admin badge for administrators
- Profile and Timeline Preferences
- Theme (Light, Dark or System) and Language
- Help & Support and Logout
- The GeoPulse version, with a link when a new version is available
- For administrators: shortcuts to Admin Dashboard and System Settings
Side menu:
- Four groups: Timeline, Explore, Organize & Share and Data (Location Sources, GPS Data, Geocoding, Export / Import)
- Administrators also see an Administration group listing every admin page, so each one is a single click away
- Notifications is no longer in the side menu; use the bell in the top bar. On phones, where the bell is hidden to save space, your avatar shows the unread count and the avatar menu has a Notifications entry.
- Home is no longer in the menu; click the GeoPulse logo in the top bar instead
- Some items were renamed: AI Chat is now AI Assistant, Geocoding Management is now Geocoding, and Favorites Management is now Favorites
Where settings moved:
- Profile tabs were regrouped. Display is split into Timeline & Map and Appearance, and the AI Assistant, Immich and Memos settings are now under Connected Apps. Old links to these tabs still open the right place, and settings search finds every setting by name.
- Backup & Restore moved from an Admin Settings tab to its own Backups & Restore page in the Administration menu.
Sessions and single sign-out
Sign-in sessions are now stored on the server instead of in signed tokens. The browser keeps only an opaque session cookie, and every request checks the session, so ending a session takes effect right away:
- Sign out ends the session on the server. Before, it only cleared the browser cookies.
- Changing your password signs out your other devices.
- Disabling a user or resetting a user's password in the Admin panel signs that user out everywhere.
- Role changes apply immediately, without signing in again.
GeoPulse now also supports OIDC single logout (#628):
- Back-channel logout: when you sign out of your identity provider (for example Authentik 2025.10+ or Keycloak), the provider tells GeoPulse, and your GeoPulse sessions from that provider end too. Configure the provider's back-channel logout URI as
https://<your-geopulse>/api/v1/auth/oidc/backchannel-logouts/<provider-name>. - RP-initiated logout: signing out of GeoPulse also signs you out of the provider, when the provider supports it. This is best effort: with providers that don't support it, sign-out works as before. It can be turned off in Admin → Settings → Authentication or with
GEOPULSE_OIDC_RP_LOGOUT_ENABLED=false.
See OIDC Single Logout for the provider setup.
Home Assistant integration
There is now a GeoPulse integration for Home Assistant. It works in both directions with one token:
- To GeoPulse: forwards your
device_trackerandpersonentities. Locations are queued on disk and retried while GeoPulse is unreachable, and a service backfills history from the Home Assistant recorder. - From GeoPulse: shows your current stay or trip, the place you're at, today's distance and counts, and your friends on the map. It also fires events for automations: geofence enter and leave, stay and trip started or ended, and tracking lost or resumed.
To set it up, install the integration from HACS (custom repository), then in GeoPulse open Location Sources → Add New Source → Home Assistant → GeoPulse integration. GeoPulse creates the token and the location source together and shows the URL and token to paste into Home Assistant. Requires Home Assistant 2026.3 or newer. See Using Home Assistant with GeoPulse.
The existing rest_command setup keeps working and is still documented as a manual option. Scripts and other clients can now also send locations in batches of up to 1000 with POST /api/v1/gps/ingest/home-assistant/batch; points already stored are skipped, so resending a batch is safe. The Home Assistant Integration API page describes the endpoints for anyone building a similar client.
API token scopes
API tokens can now be limited to what a client needs. When creating a token in Profile → Security → API tokens, choose a preset or a custom combination of scopes:
| Scope | Allows |
|---|---|
all
| Everything the user can do, including managing API tokens and the admin API. |
read
| Reading data, and the MCP server. |
write
| Changing or deleting data and settings. |
gps:ingest
| Sending GPS points. |
export
| Creating and downloading exports. |
import
| Uploading files and running imports. |
Presets: Full access, Read only (dashboards, MCP clients), Home Assistant (read + gps:ingest) and Backup (read + export).
- Tokens created before this release have full access, so existing scripts keep working.
- Scopes are fixed once a token is created; to change access, create a new token and revoke the old one.
- Token management, password changes, sign-in sessions and the admin API always need full access, so a leaked limited token cannot create new tokens or reach the admin area.
- Limited tokens never see stored credentials of other integrations.
- A request outside the token's scopes gets
403with the codeAPI_TOKEN_SCOPE_REQUIREDand the missing scope inrequiredScope. Clients can check their own token withGET /api/v1/api-tokens/current.
See API Tokens.
New features
Timeline map
- 3D map support, including 3D buildings.
- Map appearance settings: choose the map color scheme and the colors of the trip path and the highlighted path. Shared links use the same appearance.
Trip Plans
- Place discovery: find notable places worth visiting near your trip, with photos. Places come from Wikidata and photos from Wikimedia Commons, with attribution.
- Travel mode for each planned stop, with routed legs between stops (uses Valhalla when configured).
- Reorder planned stops.
Local time at each place
- New Timeline time zone setting in your profile. Profile timezone is the default and keeps the current behavior. Local time at each place shows timeline and GPS data times in the local time where each place was visited, which is handy after traveling across time zones.
- Days are still grouped by your profile timezone. The local timezone comes from the nearest GeoNames city, so times near timezone borders may be approximate. Hover a time to see where its timezone came from.
- If GeoNames city data is not loaded on the server, or the nearest city is too far away (open sea, polar regions), times are shown in your profile timezone. Administrators can turn the feature off for everyone with
GEOPULSE_TIMEZONE_LOCATION_ENABLED=false.
Timeline and classification
- New Public Transport trip type (disabled by default; enable it in Timeline Preferences or with
GEOPULSE_TIMELINE_PUBLIC_TRANSPORTATION_ENABLED). - Map matching can be turned off for specific trip types.
- A new Stay card context menu explains how map matching processed the surrounding trips.
- Period Tags are now called Timeline Labels.
Notifications
- New in-app notifications: a new GeoPulse release is available, no GPS data received, and your Rewind is ready.
- Backup notifications. Backup incident notifications are sent only when backups are scheduled.
Sharing
- Shared links can auto-refresh and auto-follow the latest location.
- Immich photos now work in shared links, and you can restrict a shared link to a single album.
Location Sources
- Each source card shows when data was last received, the newest stored point when it lags behind, and the last skipped point with the reason (for example a duplicate, or accuracy or speed above the source's filter). A wrong password for a source's username is shown too. This helps when a tracker is set up but nothing appears on the timeline.
Administration and observability
- Backend logs are structured JSON by default in production. Every request gets a
requestId, and errors get anerrorId. - Sanitized HTTP access logs: health checks, metrics, invitation links and share tokens are excluded.
- The log level can be changed at runtime in Admin → System → Observability, with no restart.
- Mosquitto logs to stdout and is much quieter by default. For temporary debugging, turn on
GEOPULSE_MQTT_VERBOSE_LOGGING(Docker) ormosquitto.verboseLogging(Helm).
Performance
- GPS ingestion with Basic Auth (OwnTracks, GPSLogger, Colota) caches password verification. Frequent location updates use far less CPU.
Rebuilt REST API
The REST API was reimplemented from scratch so it is structured, consistent and predictable:
- All endpoints live under
/api/v1, with resource-oriented paths and consistent HTTP methods and status codes. - Every endpoint has proper request and response schemas, published as an OpenAPI specification.
- Errors use one structured format (RFC 7807 problem details) with a
requestIdand anerrorId. A failed request can be matched to the exact backend log entry. - New REST API documentation with curl examples for common tasks such as reading the timeline, reading GPS points, and importing and exporting GPX/OwnTracks files.
Global CSS and theming cleanup
The frontend styling was refactored from the ground up. Old overrides, duplicated styles and broken selectors were replaced with shared design tokens, CSS layers, a corrected PrimeVue theme preset, and shared page, card and table styles. As a result, dark mode is consistent across the app, and many small visual glitches are gone.
Upgrading
The upgrade is safe for existing installations. The database migrates automatically on first start. Sessions from 1.x are not carried over, so everyone signs in once after the upgrade. Old GPS device URLs, the old health endpoint and the old metrics endpoint keep working. Make sure the frontend and the backend run the same version. As always, make a backup before a major upgrade.
The upgrade has two separate parts. Do them in this order:
- Update GeoPulse to 2.0.0. Required. Your database stays on PostgreSQL 17; nothing about PostgreSQL changes.
- Move the database to PostgreSQL 18. Optional. Do it later, whenever it suits you, with the upgrade script. See PostgreSQL 18 (optional).
Important
Do not change the PostgreSQL image yourself. PostgreSQL 18 cannot open PostgreSQL 17 data, so an existing database does not start on a PostgreSQL 18 image. The upgrade script copies the data and switches the image for you.
Docker Compose and Unraid
Step 1: Update GeoPulse (required)
Keep your current docker-compose.yml. It works with GeoPulse 2.0.0 as it is (the old /api/health healthcheck path is still served), and it keeps your database on PostgreSQL 17. Do not download the new compose file in this step.
- In your GeoPulse folder, set
GEOPULSE_VERSION=2.0.0in.env. - Run:
docker compose pull docker compose up -d
- When
docker compose psshows the containers as up (geopulse-backendashealthy, if your file has a healthcheck), open GeoPulse and sign in.
Important
Clear cache in browser might be required (one time operation).
On Unraid, Community Apps installations update as usual from the Unraid web UI; the installer keeps your compose file. For manual installations, set GEOPULSE_VERSION=2.0.0 in /mnt/user/appdata/geopulse/.env, then pull and recreate the stack in Compose Manager Plus.
That's the whole required upgrade. Steps 2 and 3 are optional.
Step 2: Move the database to PostgreSQL 18 (optional)
Run upgrade-postgres-18.sh whenever it suits you, as described in PostgreSQL 18 (optional). Besides moving the data, the script updates the PostgreSQL image and volume lines in your compose file.
Step 3: Switch to the new compose file (optional, only after step 2)
The new compose files have a few improvements:
- The backend healthcheck uses
/api/v1/system/health. - Container logs are rotated (10 MB × 3 files per container).
- The backend runs with
init: true, and PostgreSQL getsshm_size: 256mb. - Mosquitto (complete stack) logs to stdout instead of
./mosquitto/log.
They use PostgreSQL 18, so switch to them only when your database already runs PostgreSQL 18 (step 2 done):
- Keep a copy of your current
docker-compose.yml. - Download the new
docker-compose.yml(ordocker-compose-complete.yml/ the Unraid variant) into the same folder, and carry over your own changes, such as ports, the backend image variant (-native-compat), the ARM64 PostGIS image or extra services. - Run
docker compose up -d.
Staying on PostgreSQL 17 but want the new file anyway? Then keep the image and volumes lines of your current geopulse-postgres service in it. In the standard file they are:
image: postgis/postgis:17-3.5
volumes:
- postgres-data:/var/lib/postgresql/dataNote
PostgreSQL does not start after you replaced the compose file? If docker logs geopulse-postgres shows in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster", the compose file was switched to PostgreSQL 18 before the database was upgraded. Nothing was changed or lost. Put your previous compose file back (or restore the two lines above), run docker compose up -d, and continue with step 2.
Optional cleanup
The geopulse-keygen service now creates only the encryption key (keys/ai-encryption-key.txt). GeoPulse no longer uses the jwt-private-key.pem and jwt-public-key.pem files, so you can delete them. Keep the keys folder and the encryption key: they protect stored secrets such as OIDC client secrets and integration credentials.
Kubernetes (Helm)
Step 1: Update GeoPulse (required)
The chart now defaults to PostgreSQL 18, so pin PostgreSQL 17 in your values file first. Skip the pin if you use an external database (postgres.enabled=false). If your values file already sets postgres.image.tag, keep that value.
postgres:
image:
tag: "17-3.5"Then upgrade:
helm repo update
helm upgrade <release> geopulse/geopulse -n <namespace> -f your-values.yamlCaution
Already upgraded without the pin? The postgres pod does not start and logs database files are incompatible with server. No data is lost. See Upgraded the chart first? to go back, then add the pin.
Other changes in the chart:
- The chart's default liveness and readiness probes already use
/api/v1/system/health. If your values file overrides the probe paths, update them to/api/v1/system/health. - If you scrape metrics (Prometheus
ServiceMonitoror a scrape config), use/api/v1/system/metrics. - Mosquitto now logs to stdout. The
mosquitto-logsPersistentVolumeClaim and themosquitto.persistence.logs.*values were removed. The upgrade deletes that PVC, which only held old broker log files. New value:mosquitto.verboseLogging(defaultfalse). - The Helm repository is published at
https://geopulse.cc/charts. - The
config.jwt.*values were removed, and the key init container now creates only the encryption key. Session lifetimes are set with the newconfig.session.*values; RP-initiated logout withconfig.oidc.rpLogoutEnabled.
Step 2: Move the database to PostgreSQL 18 (optional, after step 1)
From a machine with kubectl and helm access to the cluster, run the Helm upgrade script with the chart version you now run:
curl -L -o upgrade-postgres-18.sh https://raw.githubusercontent.com/tess1o/GeoPulse/main/helm/upgrade-postgres-18.sh
chmod +x upgrade-postgres-18.sh
./upgrade-postgres-18.sh --release <release> --namespace <namespace> \
--chart geopulse/geopulse --chart-version 2.0.0When the script finishes, change the pin in your values file to tag: "18-3.6" (or remove it to use the chart default). The script switches the running release to PostgreSQL 18, but your values file still says 17, and the next helm upgrade -f your-values.yaml would switch back to PostgreSQL 17, which cannot open the upgraded data.
Manual installation and Proxmox LXC
- Add the
.mjslocation below to your nginx configuration (/etc/nginx/sites-available/geopulseor/etc/nginx/conf.d/geopulse.conf; on Proxmox LXC,/etc/nginx/sites-available/geopulse.conf). Put it next to the static assets location, then runsudo nginx -t && sudo systemctl reload nginx. - Stop the backend, and replace the backend binary (or JVM build) and the frontend files with the 2.0.0 artifacts. Clear
/var/www/geopulsebefore extracting the new frontend archive, so no old assets remain. - Start the backend and check it with
curl http://localhost:8080/api/v1/system/health.
Caution
The nginx change is required. The new MapLibre version loads its map worker from a .mjs file. nginx's stock mime.types has no entry for .mjs, so without this block the worker is served as application/octet-stream and browsers refuse to start it: the vector and 3D maps do not load.
location ~* ^/(?!osm/).*\.mjs$ {
types {
application/javascript mjs;
}
expires 1y;
add_header Cache-Control "public, max-age=31536000";
}The rest of the nginx configuration, including the location /api/ proxy, works unchanged. You can remove the GEOPULSE_JWT_* lines from /etc/geopulse/geopulse.env and delete the jwt-*.pem files from /opt/geopulse/keys; keep ai-encryption-key.txt. Backend output is now JSON-formatted. Set GEOPULSE_LOG_JSON_ENABLED=false in /etc/geopulse/geopulse.env if you prefer plain-text logs.
Your PostgreSQL server keeps its current version. Upgrading it is optional and separate; see PostgreSQL 18 (optional).
PostgreSQL 18 (optional)
New installations use PostgreSQL 18 with PostGIS 3.6. Existing installations keep running PostgreSQL 17 with PostGIS 3.5 after updating GeoPulse. GeoPulse supports both, including full backups and restores, so there is no deadline.
Update GeoPulse to 2.0.0 first. The upgrade scripts use the PostgreSQL 18 tools in the 2.0.0 backend image and stop with an error if the backend runs an older version. Keep GeoPulse on the same version during the database upgrade.
PostgreSQL 18 cannot open PostgreSQL 17 data files, so the database has to be copied. The script dumps the database, restores it into PostgreSQL 18 next to the old data, compares the two, and only then switches over. If GeoPulse is not healthy after the switch, it rolls back to PostgreSQL 17 automatically. The PostgreSQL 17 files are kept until you run --cleanup, and --rollback switches back at any time.
| Installation | What to do |
|---|---|
| Docker Compose or Unraid | Run upgrade-postgres-18.sh (below). It also updates your compose file; afterwards you can switch to the new compose file.
|
| Helm | Run helm/upgrade-postgres-18.sh, then change the pin in your values file. See Kubernetes (Helm), step 2.
|
| External or managed PostgreSQL | Nothing. Upgrade the database server with your provider's tools. |
| Manual / Proxmox LXC | Upgrade the cluster with pg_upgradecluster.
|
For Docker Compose and Unraid, run it on the Docker host, in the folder with your docker-compose.yml and .env (on Unraid, /mnt/user/appdata/geopulse). The script shows what it found and asks before changing anything:
cd /opt/geopulse # your GeoPulse folder
curl -L -o upgrade-postgres-18.sh https://raw.githubusercontent.com/tess1o/GeoPulse/main/upgrade-postgres-18.sh
chmod +x upgrade-postgres-18.sh
./upgrade-postgres-18.shGeoPulse is offline during the upgrade, and the data volume needs room for a second copy of the database. Backups made on PostgreSQL 17 can be restored on PostgreSQL 18, but not the other way round.
Read the PostgreSQL 18 upgrade guide before you start. It covers each installation type, several instances on one host, ARM64, manual steps and troubleshooting.
Recommended for everyone
- GPS devices and apps: the old URLs such as
/api/owntracksstill work, but they are deprecated and log a warning on each request. When convenient, switch your devices to the new URL shown on the Location Sources page, for example/api/v1/gps/ingest/owntracks. - Scripts and integrations using API tokens: the rest of the REST API moved to
/api/v1with new paths. Update your scripts using the API reference. Existing tokens keep full access; consider replacing them with tokens limited to the scopes each script needs. - Home Assistant users: your
rest_commandsetup keeps working. To also get your status, friends and events in Home Assistant, switch to the GeoPulse integration. - Monitoring: point uptime monitors to
/api/v1/system/healthand Prometheus to/api/v1/system/metrics. - OIDC providers: to use single logout, set the back-channel logout URI in your provider (see Sessions and single sign-out), and add
https://<your-geopulse>/loginto the client's allowed post-logout redirect URIs. Without that registration the provider still signs you out but shows its own page instead of returning to GeoPulse. - Scripts using API sessions (
POST /api/v1/auth/api-sessions): access and refresh tokens are now opaque strings instead of JWTs, and each refresh token works once. Use theexpiresInfield instead of decoding the token. API tokens are not affected. - After the upgrade, reload GeoPulse in your browser (or reopen the installed app) so it loads the new version.
New configuration
All settings are optional, and the defaults keep the current behavior, with one exception: a sign-in session now ends at the latest 90 days after sign-in, where 1.x sessions could renew indefinitely.
| Variable | Default | Purpose |
|---|---|---|
GEOPULSE_LOG_LEVEL
| (unset) | Default application log level (ERROR, WARN, INFO, DEBUG). Can also be changed in the Admin panel.
|
GEOPULSE_LOG_JSON_ENABLED
| true
| JSON-formatted backend logs. |
GEOPULSE_HTTP_ACCESS_LOG_ENABLED
| true
| Sanitized HTTP access log. |
GEOPULSE_MQTT_VERBOSE_LOGGING
| false
| Verbose Mosquitto logs, for temporary troubleshooting only. |
GEOPULSE_POI_ENABLED
| true
| Place discovery in Trip Plans. Calls Wikidata and Wikimedia Commons; set to false for offline or air-gapped installations.
|
GEOPULSE_POI_*
| Endpoints, language, cache lifetimes and attribution for place discovery. Also available in Admin Dashboard → System Settings → Place discovery. | |
GEOPULSE_TIMELINE_PUBLIC_TRANSPORTATION_ENABLED
| false
| Enables the Public Transport trip type by default. |
GEOPULSE_TIMEZONE_LOCATION_ENABLED
| true
| Lets users choose Local time at each place. Set to false to show every user's times in their profile timezone.
|
GEOPULSE_TIMEZONE_LOCATION_MAX_DISTANCE_KM
| 200
| Cities farther away than this are ignored for local time, and the profile timezone is used instead. |
GEOPULSE_PROXY_ADDRESS_FORWARDING / GEOPULSE_TRUSTED_PROXIES
| false / (empty)
| Trust X-Forwarded-* headers from your reverse proxy.
|
GEOPULSE_AUTH_SESSION_IDLE_TIMEOUT
| 7d
| A sign-in session ends after this long without use (the same as the 1.x refresh token lifetime). |
GEOPULSE_AUTH_SESSION_ABSOLUTE_TIMEOUT
| 90d
| A sign-in session ends at the latest this long after sign-in. |
GEOPULSE_AUTH_ACCESS_TOKEN_LIFESPAN
| 30m
| Access token lifetime for API sessions and the mobile app. |
GEOPULSE_AUTH_SESSION_CLEANUP_RETENTION
| 7d
| How long ended sessions are kept before they are deleted. |
GEOPULSE_OIDC_RP_LOGOUT_ENABLED
| true
| Also sign out of the OIDC provider when signing out of GeoPulse, if the provider supports it. |
See the environment variables reference for the full list.
Removed configuration
GeoPulse no longer signs JWTs, so these settings are ignored and can be removed: GEOPULSE_JWT_ACCESS_TOKEN_LIFESPAN, GEOPULSE_JWT_REFRESH_TOKEN_LIFESPAN, GEOPULSE_JWT_ISSUER, GEOPULSE_JWT_PUBLIC_KEY_LOCATION and GEOPULSE_JWT_PRIVATE_KEY_LOCATION (Helm: config.jwt.*).
Fixes
- Signing out of the OIDC provider now signs you out of GeoPulse (#628).
- Fixed an extra walking trip that spanned a data gap and overlapped the real trip (#627).
- Fixed manual trip split behavior (#600).
- Fixed Overland requests with an empty message (#619).
- Fixed map matching backlog re-processing.
- Fixed Notes display on data gaps.
- Fixed overlapping timeline icons.
- Fixed Immich photos in shared links.
- Updated the minimum speed for flight detection.
- Many localization, dark mode and mobile layout fixes.
Under the hood
- Upgraded to Quarkus 3.40 LTS.
- New installations use PostgreSQL 18 with PostGIS 3.6. The GeoPulse images include the PostgreSQL 17 and 18 client tools and pick the right ones for backups automatically.
- User preferences moved to a single JSONB structure.
- Sign-in sessions are stored server-side with opaque, hashed credentials. API refresh tokens rotate on every use, and a reused refresh token ends its session. Share-link access tokens no longer use JWTs either, and the RSA key pair is no longer needed.
- New REST API integration tests and updated end-to-end tests.
- Upgraded the frontend toolchain and libraries to current stable versions, including Vite 8, Vue 3.5, Vue Router 5, Pinia 4, PrimeVue 4.5 and MapLibre GL 6. All frontend dependencies are now pinned to exact versions.
- The frontend builds on Node.js 26 and is served by nginx 1.30. Docker base images were updated and pinned to exact versions. Native images build with Mandrel 25.0.4.1.