Skip to content

Commit 99a9823

Browse files
DennisOSRMCopilot
andauthored
fix: support Docker runtime routing config with OSRM_MODES (#423)
* fix: support environment overrides at runtime via docker run -e Fixes issue #421: Environment override no longer supported. Users can now override configuration at container runtime using environment variables: docker run -e OSRM_BACKEND='http://localhost:5001' osrm-frontend This works as documented by: 1. Creating docker/entrypoint.sh that reads OSRM_* env vars at startup 2. Generating config.json from env vars before nginx starts 3. Loading config.json in index.html before the app bundle runs 4. Using config values in src/leaflet_options.js Supported environment variables: - OSRM_BACKEND: Backend routing service URL - OSRM_CENTER: Map center coordinates (lat,lng) - OSRM_ZOOM: Initial map zoom level - OSRM_LANGUAGE: UI language - OSRM_LABEL: Default routing service label - OSRM_DEFAULT_LAYER: Default map layer Build-time configuration still supported via --build-arg. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review comments for runtime env config - Fix entrypoint.sh to accept CMD and exec "$@" for standard Docker behavior - Add JSON escaping in entrypoint.sh to handle special characters in config values - Validate OSRM_ZOOM to ensure numeric output in JSON - Change default OSRM_BACKEND from router.project-osrm.org to localhost:5000 (matches README.md documentation) - Replace synchronous XHR with async fetch in index.html (sync XHR deprecated) - Fix 404 handling in index.html (XHR fires onload, not onerror for 404) - Add input validation to leaflet_options.js: * parseCenter() validates lat/lng, falls back to defaults if invalid * getZoom() validates numeric value, falls back if NaN - Add comprehensive test coverage for config overrides in leaflet_options.test.js: * Tests for OSRM_BACKEND, OSRM_CENTER, OSRM_ZOOM, OSRM_LANGUAGE * Tests for invalid/edge case values with fallback behavior * 13 new tests added (now 112 total tests passing) All tests pass, linting passes, Docker builds and runs correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use configurable backend for all routing modes (bike, foot) All three routing modes (driving, bike, foot) now use the same configurable OSRM_BACKEND, defaulting to localhost:5000 as documented in README.md. Previously, Bike and Foot modes were hardcoded to routing.openstreetmap.de, making it impossible to use a local OSRM instance for all modes when deployed in Docker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: distinguish Docker vs local dev backends Introduce OSRM_ENVIRONMENT to differentiate execution context: - Docker: All modes (driving, bike, foot) use localhost:5000 backend - Local dev: Bike/foot use public routing.openstreetmap.de services Changes: - Add OSRM_ENVIRONMENT env var to Dockerfile (set to 'docker') - Include OSRM_ENVIRONMENT in generated config.json via entrypoint - Add getAlternativeBackend() that returns backend for bike/foot based on environment - Update services array to use public routing services in dev, localhost in Docker Benefits: - Docker users can run full-featured local OSRM stack without modification - Local dev still works with public services (bike/foot) when no local OSRM installed - No breaking changes to existing behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use public routing services in dev mode In local dev (no OSRM_ENVIRONMENT), all routing modes now use public services: - Driving: router.project-osrm.org - Bike: routing.openstreetmap.de/routed-bike - Foot: routing.openstreetmap.de/routed-foot In Docker (OSRM_ENVIRONMENT=docker), all modes use localhost:5000 backend for a fully local setup. Updates: - Refactor getBackend() to return public service URL in dev mode - Update tests to expect public services in dev, localhost in Docker - Add test for Docker mode explicitly Now 113 tests pass (1 additional test for Docker mode). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: support per-mode backend configuration - Add OSRM_BACKEND_DRIVING, OSRM_BACKEND_BIKE, OSRM_BACKEND_FOOT env vars - Each mode can now have its own backend URL override - In Docker: all modes default to localhost:5000, but can be customized per mode - In dev: all three modes available (driving via router.project-osrm.org, bike/foot via routing.openstreetmap.de) - Backward compatible: OSRM_BACKEND still works as fallback for all modes - Add 6 tests covering per-mode backend configuration and dev mode availability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: support freely-named routing modes via OSRM_MODES JSON Allow users to define custom names and URLs for routing modes by providing /etc/osrm/modes.json with a JSON array of {name, url} pairs. Example modes.json: [ { "name": "Car (fastest)", "url": "http://localhost:5000" }, { "name": "Scenic Route", "url": "http://localhost:5001" }, { "name": "Walking", "url": "http://localhost:5000" } ] - Entrypoint now reads /etc/osrm/modes.json at startup (Docker only) - Falls back to default modes if file not present: - Docker: Car, Bike, Foot (all using localhost:5000) - Dev: Car (router.project-osrm.org), Bike (routing.openstreetmap.de), Foot (routing.openstreetmap.de) - leaflet_options.js now parses OSRM_MODES from config - Each mode internally mapped to a routing profile (driving, bike, foot) - Respects OSRM_BACKEND for first mode's URL in default config - Add 7 tests covering custom modes, default fallbacks, and JSON parsing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: support OSRM_MODES via environment variable for docker run Users can now provide modes directly via docker run without mounting a file: docker run -e OSRM_MODES='[{"name":"Car","url":"http://localhost:5000"}]' osrm-frontend Priority order for modes configuration: 1. OSRM_MODES environment variable (highest priority) 2. /etc/osrm/modes.json file (if mounted via -v volume) 3. Default modes (car/bike/foot using localhost:5000) This makes it convenient for both quick testing and production deployments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: correct default modes for dev vs Docker Dev mode (not in Docker): - Uses three public routing profiles: driving, bike, foot - Unless OSRM_BACKEND is explicitly provided (then uses just that one) Docker mode: - Uses single 'default' profile pointing to localhost:5000 - Can be overridden via OSRM_BACKEND env var or OSRM_MODES JSON This ensures: - Dev users see all three public OSRM services - Docker users get a simple single-mode setup by default - Either can be customized via environment variables Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove localhost default from dev config Dev mode (npm start) should use public OSRM services, not localhost. Removed OSRM_BACKEND and OSRM_LABEL from index.html defaults so that in dev mode, parseModes() correctly defaults to three public profiles (driving via router.project-osrm.org, bike/foot via routing.openstreetmap.de). Only Docker mode should default to localhost:5000. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add backward compatibility for OSRM_BACKEND with deprecation warning Supports both old (OSRM_BACKEND) and new (OSRM_MODES) environment variables: Priority order: 1. OSRM_MODES (new JSON-based modes, highest priority) 2. OSRM_BACKEND (legacy, triggers deprecation warning) 3. Defaults (public services in dev, localhost in Docker) Deprecation behavior: - If only OSRM_BACKEND is set: creates single 'default' mode, warns to migrate - If only OSRM_MODES is set: uses new behavior, no warning - If both are set: uses OSRM_MODES, warns about both being configured Example migrations: Old: docker run -e OSRM_BACKEND='http://localhost:5000' New: docker run -e OSRM_MODES='[{"name":"default","url":"http://localhost:5000"}]' Add 3 tests verifying backward compat and deprecation warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * debug: add console logging to diagnose bike/foot backend routing issue Add detailed logging to parseModes() and buildServices() to see: - What config is being read - Which code path is taken (dev vs docker) - What services array is built This will help diagnose why bike and foot profiles are routing to localhost instead of routing.openstreetmap.de in dev mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: correct routing backends for all three modes Dev mode (npm start): - driving: router.project-osrm.org - bike: routing.openstreetmap.de/routed-bike with correct path - foot: routing.openstreetmap.de/routed-foot with correct path Docker mode (default): - Single 'default' mode using localhost:5000 - Entrypoint prioritizes: OSRM_MODES > /etc/osrm/modes.json > OSRM_BACKEND > localhost:5000 Fixed entrypoint logic to properly handle OSRM_BACKEND default value without overriding it prematurely. Removed debug logging - all 120 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Docker defaults to public demo instances like dev mode Docker now defaults to the same three public profiles as dev mode: - driving: router.project-osrm.org - bike: routing.openstreetmap.de/routed-bike - foot: routing.openstreetmap.de/routed-foot Users can override with: - OSRM_MODES=... (JSON) - OSRM_BACKEND=... (legacy, single mode) - /etc/osrm/modes.json (file mount) All 120 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: revert Docker to localhost:5000 by default Docker container should default to localhost:5000 with single 'default' profile unless explicitly configured with env vars. Dev mode (npm start) continues to use public demo instances. The distinction is controlled by OSRM_ENVIRONMENT: - If 'docker' (set by entrypoint): use localhost:5000 - Otherwise (dev/browser): use public instances All 120 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: make services lazy-loaded to respect runtime config Changed services from being computed at module load time to being computed lazily via a getter. This ensures that when buildServices() is called, window.osrmConfig has already been updated by config.json loading (in Docker) or remains with dev defaults (in npm start). This fixes the issue where Docker was reading config before OSRM_ENVIRONMENT was set from the loaded config.json. All 120 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: read config fresh from window.osrmConfig on each call parseModes() now reads from window.osrmConfig directly instead of using the captured config variable. Combined with lazy-loading via getter, this ensures that OSRM_ENVIRONMENT and other config values are always read from the current window.osrmConfig, which gets updated when config.json is loaded. This fixes Docker container defaulting to public instances instead of localhost:5000 - it now correctly detects OSRM_ENVIRONMENT='docker' from the loaded config.json. All 120 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: support Docker runtime routing config - keep Docker default routing on http://localhost:5000 with a single default profile - support JSON-based OSRM_MODES runtime configuration for multiple profiles - keep OSRM_BACKEND as a deprecated single-backend fallback with warnings - document precedence and Docker usage examples - add tests for deprecation warnings and entrypoint config generation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent aaee5ad commit 99a9823

7 files changed

Lines changed: 716 additions & 27 deletions

File tree

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,33 @@ Serves the frontend at `http://localhost:9966` running queries against the routi
1616
docker run -p 9966:9966 ghcr.io/project-osrm/osrm-frontend:latest
1717
```
1818

19-
Per default routing requests are made against the backend at `http://localhost:5000`.
20-
You can change the backend by using `-e OSRM_BACKEND='http://localhost:5001'` in the `docker run` command.
19+
By default Docker uses a single routing profile named `default` and sends requests to `http://localhost:5000`.
20+
21+
### Recommended: multiple profiles with `OSRM_MODES`
22+
23+
`OSRM_MODES` is the current Docker runtime configuration interface. It accepts a JSON array of objects with `name` and `url` fields.
24+
25+
```bash
26+
docker run -p 9966:9966 \
27+
-e 'OSRM_MODES=[{"name":"car","url":"https://routing.openstreetmap.de/routed-car"},{"name":"foot","url":"https://routing.openstreetmap.de/routed-foot"},{"name":"bike","url":"https://routing.openstreetmap.de/routed-bike"}]' \
28+
ghcr.io/project-osrm/osrm-frontend:latest
29+
```
30+
31+
### Deprecated: single backend with `OSRM_BACKEND`
32+
33+
`OSRM_BACKEND` is deprecated. It is still supported for backward compatibility and configures exactly one backend named `default`.
34+
35+
```bash
36+
docker run -p 9966:9966 \
37+
-e OSRM_BACKEND='http://localhost:5001' \
38+
ghcr.io/project-osrm/osrm-frontend:latest
39+
```
40+
41+
### Precedence
42+
43+
1. If only `OSRM_BACKEND` is set, the frontend configures one backend and emits a deprecation warning.
44+
2. If only `OSRM_MODES` is set, the frontend parses the JSON and configures the listed modes.
45+
3. If both are set, `OSRM_MODES` wins and the frontend emits a deprecation warning for `OSRM_BACKEND`.
2146

2247
In case Docker complains about not being able to connect to the Docker daemon make sure you are in the `docker` group.
2348

@@ -28,7 +53,7 @@ sudo usermod -aG docker $USER
2853
To build the docker image locally:
2954

3055
```bash
31-
docker build . -f docker/Dockerfile -t osrm-frontend
56+
docker build -f docker/Dockerfile -t osrm-frontend .
3257
docker run -p 9966:9966 osrm-frontend
3358
```
3459

@@ -56,7 +81,9 @@ npm run start-index
5681

5782
## Changing Backends
5883

59-
In `src/leaflet_options.js` adjust:
84+
For Docker deployments, prefer runtime configuration via `OSRM_MODES` instead of editing source files.
85+
86+
For source-level customization, adjust `src/leaflet_options.js`:
6087

6188
```
6289
services: [{

docker/Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,23 @@ RUN npm run build
2525

2626
# Stage 2: serve
2727
FROM nginx:alpine
28+
29+
# Set runtime environment defaults for entrypoint
30+
# Note: Default OSRM_BACKEND is localhost:5000 (as documented in README.md:19)
31+
# Override with -e OSRM_BACKEND='http://...' at runtime
32+
ENV OSRM_ENVIRONMENT=docker \
33+
OSRM_BACKEND=http://localhost:5000 \
34+
OSRM_CENTER=38.8995,-77.0269 \
35+
OSRM_ZOOM=13 \
36+
OSRM_LANGUAGE=en \
37+
OSRM_LABEL="Car (fastest)" \
38+
OSRM_DEFAULT_LAYER=streets \
39+
OSRM_PROFILES=driving,bike,foot
40+
2841
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
42+
COPY docker/entrypoint.sh /docker-entrypoint.sh
43+
RUN chmod +x /docker-entrypoint.sh
44+
2945
# Webpack outputs bundle.js to the project root; copy all static assets
3046
COPY --from=build /app/index.html /usr/share/nginx/html/
3147
COPY --from=build /app/favicon.ico /usr/share/nginx/html/
@@ -37,4 +53,7 @@ COPY --from=build /app/fonts /usr/share/nginx/html/fonts
3753
COPY --from=build /app/images /usr/share/nginx/html/images
3854
COPY --from=build /app/i18n /usr/share/nginx/html/i18n
3955
COPY --from=build /app/debug /usr/share/nginx/html/debug
56+
4057
EXPOSE 9966
58+
ENTRYPOINT ["/docker-entrypoint.sh"]
59+
CMD ["nginx", "-g", "daemon off;"]

docker/entrypoint.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Default values
5+
OSRM_BACKEND="${OSRM_BACKEND:-http://localhost:5000}"
6+
OSRM_CENTER="${OSRM_CENTER:-38.8995,-77.0269}"
7+
OSRM_ZOOM="${OSRM_ZOOM:-13}"
8+
OSRM_LANGUAGE="${OSRM_LANGUAGE:-en}"
9+
OSRM_DEFAULT_LAYER="${OSRM_DEFAULT_LAYER:-streets}"
10+
OSRM_ENVIRONMENT="${OSRM_ENVIRONMENT:-docker}"
11+
12+
# Validate OSRM_ZOOM is numeric (for valid JSON output)
13+
case "$OSRM_ZOOM" in
14+
''|*[!0-9-]*|-) OSRM_ZOOM=13 ;;
15+
esac
16+
17+
# Escape JSON string values (handle quotes, newlines, backslashes)
18+
escape_json() {
19+
printf '%s\n' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\/' | tr -d '\n' | sed 's/\\$//'
20+
}
21+
22+
# Load routing modes from the preferred OSRM_MODES config.
23+
# OSRM_BACKEND is deprecated and only kept as a single-backend fallback.
24+
MODES_JSON=""
25+
CONFIG_BACKEND=""
26+
27+
if [ -n "$OSRM_MODES" ]; then
28+
# Use the JSON-based mode configuration directly.
29+
MODES_JSON="$OSRM_MODES"
30+
if [ "$OSRM_BACKEND" != "http://localhost:5000" ]; then
31+
CONFIG_BACKEND="$OSRM_BACKEND"
32+
fi
33+
elif [ -f /etc/osrm/modes.json ]; then
34+
# Fall back to a mounted JSON file with the same shape as OSRM_MODES.
35+
MODES_JSON=$(cat /etc/osrm/modes.json)
36+
if [ "$OSRM_BACKEND" != "http://localhost:5000" ]; then
37+
CONFIG_BACKEND="$OSRM_BACKEND"
38+
fi
39+
elif [ -n "$OSRM_BACKEND" ] && [ "$OSRM_BACKEND" != "http://localhost:5000" ]; then
40+
# Backward compatibility: a non-default OSRM_BACKEND means one deprecated single backend.
41+
CONFIG_BACKEND="$OSRM_BACKEND"
42+
else
43+
# With no runtime override, keep the Docker default:
44+
# one profile named "default" pointing at http://localhost:5000.
45+
MODES_JSON=""
46+
fi
47+
48+
# Generate config.json with proper JSON escaping
49+
cat > /usr/share/nginx/html/config.json << EOF
50+
{
51+
"OSRM_BACKEND": "$(escape_json "$CONFIG_BACKEND")",
52+
"OSRM_CENTER": "$(escape_json "$OSRM_CENTER")",
53+
"OSRM_ZOOM": $OSRM_ZOOM,
54+
"OSRM_LANGUAGE": "$(escape_json "$OSRM_LANGUAGE")",
55+
"OSRM_DEFAULT_LAYER": "$(escape_json "$OSRM_DEFAULT_LAYER")",
56+
"OSRM_ENVIRONMENT": "$(escape_json "$OSRM_ENVIRONMENT")",
57+
"OSRM_MODES": "$(escape_json "$MODES_JSON")"
58+
}
59+
EOF
60+
61+
# Execute the default command (nginx) or any command passed to the container
62+
if [ "$#" -eq 0 ]; then
63+
exec nginx -g "daemon off;"
64+
else
65+
exec "$@"
66+
fi

index.html

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,55 @@
1212
</head>
1313
<body>
1414
<div id='map' class='map'></div>
15-
<script src='bundle.js'></script>
15+
<script>
16+
// Load runtime configuration (generated by Docker entrypoint from OSRM_* env vars)
17+
// Falls back to bundled defaults if config.json is not available
18+
// OSRM_ENVIRONMENT indicates execution context: 'docker' or undefined (local dev)
19+
window.osrmConfig = {
20+
// OSRM_ENVIRONMENT only set to 'docker' if explicitly running in Docker
21+
// In all other cases (dev, browser), leave it undefined to use public services
22+
OSRM_CENTER: '38.8995,-77.0269',
23+
OSRM_ZOOM: 13,
24+
OSRM_LANGUAGE: 'en',
25+
OSRM_DEFAULT_LAYER: 'streets'
26+
// OSRM_BACKEND and OSRM_MODES will be set by config.json if available
27+
};
28+
29+
(function() {
30+
function loadBundle() {
31+
var script = document.createElement('script');
32+
script.src = 'bundle.js';
33+
document.body.appendChild(script);
34+
}
35+
36+
function loadConfig() {
37+
return new Promise(function(resolve) {
38+
fetch('config.json')
39+
.then(function(response) {
40+
if (!response.ok) {
41+
if (response.status === 404) {
42+
console.warn('config.json not found, using defaults');
43+
} else {
44+
console.warn('Failed to load config.json (HTTP ' + response.status + '), using defaults');
45+
}
46+
resolve();
47+
return;
48+
}
49+
return response.json()
50+
.then(function(config) {
51+
window.osrmConfig = config;
52+
resolve();
53+
});
54+
})
55+
.catch(function(error) {
56+
console.warn('Failed to load config.json due to a network error, using defaults:', error);
57+
resolve();
58+
});
59+
});
60+
}
61+
62+
loadConfig().then(loadBundle);
63+
})();
64+
</script>
1665
</body>
1766
</html>

0 commit comments

Comments
 (0)