|
3 | 3 | Goal: spin up the **backend in Docker** and the **frontend natively** (live-editable |
4 | 4 | `ng serve`) and confirm they talk to each other, with the fewest moving parts. |
5 | 5 |
|
6 | | -This recipe was validated end-to-end on `internal/unify-docker-compose` (Windows + Docker |
7 | | -Desktop, Compose v2.40): BE up from one `.env.local`, FE on `ng serve` rendering in a real |
8 | | -browser against the BE (200s, CORS OK). It builds on PR #1289, which makes |
9 | | -`docker/docker-compose-rest.yml` drivable from a single `--env-file`. |
| 6 | +Validated end-to-end on `internal/unify-docker-compose` (Windows + Docker Desktop, Compose |
| 7 | +v2.40): BE up from one `.env.local`, FE on `ng serve` rendering the LINDAT/CLARIAH-CZ home in |
| 8 | +a real browser with demo content (6 communities, 471 indexed items), 200s, no CORS errors. |
| 9 | +Builds on PR #1289, which makes `docker/docker-compose-rest.yml` drivable from one `--env-file`. |
10 | 10 |
|
11 | 11 | > Several non-obvious traps below cost real time to find — read **Gotchas** before starting. |
| 12 | +> **Match the BE to the FE version:** this FE reports **7.6.5** (see the browser console |
| 13 | +> startup banner), so use a **7.6.5** backend image. A 7.5 BE (`dtq-dev-7.5`) parses most |
| 14 | +> things but errors on browse definitions. |
12 | 15 |
|
13 | 16 | --- |
14 | 17 |
|
15 | 18 | ## Prerequisites |
16 | 19 |
|
17 | 20 | - **Docker** running. |
18 | | -- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node |
19 | | - in ways CI doesn't catch (CI only runs `build:prod`, not `ng serve`). Quick portable |
20 | | - install, no global change: |
| 21 | +- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node in |
| 22 | + ways CI doesn't catch (CI runs `build:prod`, never `ng serve`). Portable install, no global |
| 23 | + change: |
21 | 24 | ```bash |
22 | 25 | D=/c/Users/$USER/AppData/Local/Temp/node18setup; mkdir -p "$D" && cd "$D" |
23 | 26 | curl -fsSL -o n18.zip https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip |
24 | 27 | /c/Windows/System32/tar.exe -xf n18.zip # MSYS tar can't unzip; use Windows bsdtar |
25 | | - ./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 # yarn into the portable prefix |
26 | | - export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for the FE commands |
| 28 | + ./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 |
| 29 | + export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for FE commands |
27 | 30 | ``` |
28 | | -- **The `ng serve` fix** (see Gotcha #2): this branch pins `copy-webpack-plugin@^6.4.1`, |
29 | | - which breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. |
| 31 | +- **The `ng serve` fix** (Gotcha #2): the branch pins `copy-webpack-plugin@^6.4.1`, which |
| 32 | + breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. (This is now |
| 33 | + committed on PR #1289.) |
30 | 34 |
|
31 | 35 | --- |
32 | 36 |
|
33 | 37 | ## TL;DR |
34 | 38 |
|
35 | 39 | ```bash |
36 | | -# 1) BACKEND in Docker — fresh DB, migrate-on-boot, ~40-90s to healthy |
37 | | -cp docker/.env.local.example docker/.env.local # then edit per "Backend" below |
38 | | -docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml up -d |
| 40 | +# 1) BACKEND in Docker, WITH demo content (7.6.5 to match the FE) — ~2-4 min first boot |
| 41 | +cp docker/.env.local.example docker/.env.local # then edit per "Backend" |
| 42 | +docker compose --env-file docker/.env.local \ |
| 43 | + -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d |
39 | 44 | # wait: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8087/server/api => 200 |
| 45 | +MSYS_NO_PATHCONV=1 docker exec dspace7 /dspace/bin/dspace index-discovery -b # populate Solr |
40 | 46 |
|
41 | | -# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already bumped + installed) |
| 47 | +# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already ^11 + installed) |
42 | 48 | DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ |
43 | 49 | DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ |
44 | | - yarn start:dev # live-reload on http://localhost:4000 |
| 50 | + yarn start:dev # live-reload on http://localhost:4000 |
45 | 51 |
|
46 | 52 | # 3) teardown (-v wipes DB/Solr volumes — see Gotcha #4) |
47 | | -docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml down -v --remove-orphans |
| 53 | +docker compose --env-file docker/.env.local \ |
| 54 | + -f docker/docker-compose-rest.yml -f docker/db.entities.yml down -v --remove-orphans |
48 | 55 | ``` |
49 | 56 |
|
50 | | -The FE config maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, |
51 | | -`rest.port`→`DSPACE_REST_PORT`, …; env wins last — see `src/config/config.server.ts`), so |
52 | | -no file edits are needed to point the FE at the dockerized BE. |
| 57 | +The FE maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, … ; env wins |
| 58 | +last — see `src/config/config.server.ts`), so no file edits are needed to point it at the BE. |
53 | 59 |
|
54 | 60 | --- |
55 | 61 |
|
56 | 62 | ## Backend (Docker) |
57 | 63 |
|
58 | | -The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up from |
59 | | -that file **alone** (no `docker-compose.yml` — that's the FE container, which we don't use). |
| 64 | +`docker/docker-compose-rest.yml` fully defines the network, so the BE comes up from that file |
| 65 | +(plus `db.entities.yml` for demo data) — no `docker-compose.yml` (that's the FE container). |
60 | 66 |
|
61 | | -`docker/.env.local` for this recipe (tuned for a **native** FE): |
| 67 | +`docker/.env.local` for this recipe: |
62 | 68 |
|
63 | 69 | ```ini |
64 | 70 | INSTANCE=7 |
65 | 71 | # 127.0.0.1, NOT localhost (Gotcha #5). BE self-links use REST_URL, so keep it on 127.0.0.1. |
66 | 72 | DSPACE_HOST=127.0.0.1 |
67 | 73 | DSPACE_REST_PORT=808${INSTANCE} # -> 8087 |
68 | | -DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning |
| 74 | +DSPACE_REST_NAMESPACE=/server |
69 | 75 | REST_URL=http://127.0.0.1:808${INSTANCE}/server |
70 | 76 | UI_URL=http://localhost:4000 # native FE dev-server port, NOT 400${INSTANCE} |
71 | | -HOST_IP=127.0.0.1 # loopback is enough for native FE; no LAN exposure |
72 | | -DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions |
| 77 | +HOST_IP=127.0.0.1 |
| 78 | +DSPACE_SUBNET_PREFIX=10.10${INSTANCE} |
73 | 79 | REST_CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000 |
74 | | -# Use the BE that MATCHES this FE branch (Gotcha #6). dtq-dev FE + dtq-dev-7.5 BE = DSpace 7.5. |
75 | | -DSPACE_REST_IMAGE=dataquest/dspace:dtq-dev-7.5 |
76 | | -DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x |
77 | | -DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x |
| 80 | + |
| 81 | +# --- Option A: demo content (VERIFIED) — upstream 7.6.5 + db.entities.yml ------------------- |
| 82 | +# Matches the FE's 7.6.5 and loads the official DSpace demo entities dataset. NOTE: this is |
| 83 | +# UPSTREAM DSpace data on an UPSTREAM BE, so CLARIN-specific FE calls 404 (harmless to render). |
| 84 | +DSPACE_REST_IMAGE=dspace/dspace:dspace-7_x |
| 85 | +DSPACE_DB_IMAGE=dspace/dspace-postgres-pgcrypto:dspace-7_x # db.entities.yml overrides to -loadsql |
| 86 | +DSPACE_SOLR_IMAGE=dspace/dspace-solr:dspace-7_x |
| 87 | +DOCKER_REGISTRY=docker.io # consumed by db.entities.yml to resolve the -loadsql image |
| 88 | +DOCKER_OWNER=dspace |
| 89 | +DSPACE_VER=dspace-7_x |
| 90 | + |
| 91 | +# --- Option B: CLARIN fidelity (version-matched, but no content unless you have a dump) ------ |
| 92 | +# DSPACE_REST_IMAGE=dataquest/dspace:dspace-7_x # DSpace 7.6.5, CLARIN tables/endpoints |
| 93 | +# DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x |
| 94 | +# DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x |
| 95 | +# Fresh DB = empty homepage. For real CLARIN content, restore a dataquest/LINDAT DB dump into |
| 96 | +# dspacedb7 (pg_restore/psql) instead of layering db.entities.yml, then reindex. |
78 | 97 | ``` |
79 | 98 |
|
| 99 | +- Bring up Option A with **both** files: `-f docker/docker-compose-rest.yml -f docker/db.entities.yml`. |
| 100 | + The `-loadsql` Postgres downloads + imports `dspace7-entities-data.sql` on first boot; the BE |
| 101 | + then runs `database migrate ignored`. **Then reindex Solr** (command above) or the homepage's |
| 102 | + browse/search/"What's New" stay empty even though the DB has data. |
| 103 | +- For Option B (no `db.entities.yml`), use just `-f docker/docker-compose-rest.yml`. |
80 | 104 | - **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`. Change |
81 | 105 | `INSTANCE` to re-target all at once. 5 and 8 are reserved by `.github/workflows/deploy.yml`. |
82 | | -- First boot runs `dspace database migrate force` on an empty DB. No content, no admin user |
83 | | - (the homepage is intentionally near-empty — see "What success looks like"). To create an |
84 | | - admin: |
85 | | - ```bash |
86 | | - docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml -f docker/cli.yml \ |
87 | | - run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest |
88 | | - ``` |
| 106 | +- Admin user (optional): `… -f docker/cli.yml run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest` |
89 | 107 |
|
90 | 108 | ### Verify the BE |
91 | 109 | ```bash |
92 | | -curl -s http://127.0.0.1:8087/server/api # dspaceName/dspaceVersion (=> DSpace 7.5) |
| 110 | +curl -s http://127.0.0.1:8087/server/api # dspaceVersion => DSpace 7.6.5 |
| 111 | +curl -s http://127.0.0.1:8087/server/api/core/communities/search/top # totalElements: 6 (demo) |
93 | 112 | curl -s -i -X OPTIONS http://127.0.0.1:8087/server/api/core/items \ |
94 | 113 | -H 'Origin: http://localhost:4000' -H 'Access-Control-Request-Method: GET' \ |
95 | 114 | | grep -i access-control-allow-origin # => http://localhost:4000 |
96 | 115 | ``` |
97 | | -(Confirmed: allowed origin → `200` + matching `Access-Control-Allow-Origin`; other origin → `403`.) |
98 | 116 |
|
99 | 117 | --- |
100 | 118 |
|
101 | 119 | ## Frontend (native, live-editable) |
102 | 120 |
|
103 | 121 | ```bash |
104 | | -# one-time: Node 18 on PATH, copy-webpack-plugin bumped to ^11, deps installed |
105 | 122 | export PATH="/c/Users/$USER/AppData/Local/Temp/node18setup/node-v18.20.5-win-x64:$PATH" |
106 | 123 | yarn install # after the copy-webpack-plugin bump, or if node_modules is partial |
107 | | - |
108 | 124 | DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ |
109 | 125 | DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ |
110 | 126 | yarn start:dev # ng serve, live-reload, http://localhost:4000 |
111 | 127 | ``` |
112 | 128 |
|
113 | | -- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds, what you want for editing). |
114 | | - `yarn start` does a full SSR production build instead (slower; for an SSR-accurate check). |
115 | | -- The browser calls the BE **directly** at `http://127.0.0.1:8087/server`, so the BE CORS list |
116 | | - must include `http://localhost:4000` (it does, above). |
117 | | -- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); the config default |
118 | | - `/server` is correct. |
| 129 | +- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds — what you want for editing). |
| 130 | + `yarn start` does a full SSR production build instead. |
| 131 | +- The browser calls the BE directly at `http://127.0.0.1:8087/server`; the CORS list must include |
| 132 | + `http://localhost:4000` (it does). |
| 133 | +- Don't set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); config default `/server` |
| 134 | + is correct. |
119 | 135 |
|
120 | 136 | ### What success looks like |
121 | | -`http://localhost:4000` renders the DSpace shell + the cookie-consent (Klaro) banner; the main |
122 | | -content area is empty because the repo is fresh. Browser DevTools → Network shows |
123 | | -`GET http://127.0.0.1:8087/server/api → 200`, `.../authn/status → 200`, |
124 | | -`.../discover/browses → 200`, `.../core/sites → 200`, with **no CORS errors**. A few benign |
125 | | -console errors are normal here: `favicon.ico` 404, Matomo refused (no analytics container), |
126 | | -`google.analytics.key` 404, and two DSpace-7.5 nuances (`/api/security/csrf` 404 and a |
127 | | -browse-definitions parse warning) — none block rendering. |
| 137 | +`http://localhost:4000` redirects to `/home` and renders the **LINDAT/CLARIAH-CZ Repository |
| 138 | +Home**: search + facets (Author/Subject/Language), a **"What's New"** list of items, and the |
| 139 | +footer. DevTools → Network shows `…8087/server/api/*` 200s with no CORS errors. Benign console |
| 140 | +errors are normal: `favicon.ico` 404, Matomo refused, `google.analytics.key` 404, `/security/csrf` |
| 141 | +404, and (with Option A's upstream BE) some CLARIN-specific 404s. |
128 | 142 |
|
129 | 143 | --- |
130 | 144 |
|
131 | 145 | ## Gotchas (each one cost time to find) |
132 | 146 |
|
133 | | -1. **Git Bash mangles `DSPACE_REST_NAMESPACE=/server`** into `C:/Program Files/Git/server` |
134 | | - (MSYS path conversion of a leading-slash value), corrupting the REST baseUrl. Fix: don't |
135 | | - pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. |
136 | | - Inside the `.env.local` file it's NOT mangled — only shell exports are. |
| 147 | +1. **Git Bash mangles leading-slash paths.** `DSPACE_REST_NAMESPACE=/server` becomes |
| 148 | + `C:/Program Files/Git/server`, and `docker exec dspace7 /dspace/bin/dspace …` becomes |
| 149 | + `…/Git/dspace/bin/dspace` (no such file). Fix: prefix the command with |
| 150 | + `MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. (Values inside the `.env.local` file are NOT |
| 151 | + mangled — only shell args.) |
137 | 152 |
|
138 | 153 | 2. **`ng serve` is broken on this branch out of the box.** `package.json` pins |
139 | | - `copy-webpack-plugin@^6.4.1`, but `@angular-builders/custom-webpack` (the serve builder) |
140 | | - resolves it for build-angular@15, which passes the `priority` option (v9+ only) → |
141 | | - `An unhandled exception occurred: Copy Plugin … unknown property 'priority'`. CI misses it |
142 | | - (it runs `build:prod`, not `ng serve`). Fix: set `"copy-webpack-plugin": "^11.0.0"` in |
143 | | - `package.json` (matches build-angular's pin) and `yarn install`. Validated: `ng serve` then |
144 | | - `✓ Compiled successfully`. (If pushing this fix, let CI re-run `build:prod` + tests.) |
| 154 | + `copy-webpack-plugin@^6.4.1`, but build-angular@15 (via `@angular-builders/custom-webpack`) |
| 155 | + emits asset-copy patterns using the `priority` option (v9+) → `Copy Plugin … unknown property |
| 156 | + 'priority'`. CI misses it (it runs `build:prod`, not `ng serve`). Fix: `^11.0.0` (matches |
| 157 | + build-angular) + `yarn install`. (Committed on PR #1289.) |
145 | 158 |
|
146 | | -3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a |
147 | | - cascade of `NG6002`/`NG8004` → run a full `yarn install`. |
| 159 | +3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a cascade |
| 160 | + of `NG6002`/`NG8004` → run a full `yarn install`. |
148 | 161 |
|
149 | | -4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by |
150 | | - a different Postgres major gives `FATAL: database files are incompatible with server`; the BE |
151 | | - then hangs forever in its DB-wait loop and REST never comes up. Fix: `down -v` for a clean |
152 | | - start. `--remove-orphans` clears a leftover `dspace-angular<INSTANCE>` container. |
| 162 | +4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by a |
| 163 | + different Postgres major gives `FATAL: database files are incompatible with server`; the BE |
| 164 | + then hangs forever in its DB-wait loop. Fix: `down -v` for a clean start (`--remove-orphans` |
| 165 | + clears a leftover `dspace-angular<INSTANCE>` container). |
153 | 166 |
|
154 | 167 | 5. **`localhost` ≠ `127.0.0.1` for the FE→BE hop.** Node 18 resolves `localhost` to IPv6 `::1` |
155 | | - with no IPv4 fallback, but the BE publishes on `127.0.0.1` only (`host_ip`), so SSR/Node |
156 | | - fetches `ECONNREFUSED` and you get `undefined doesn't contain the link sites`. Use |
157 | | - `DSPACE_REST_HOST=127.0.0.1` (and `REST_URL=http://127.0.0.1:...` so the BE's HAL self-links |
158 | | - match). `curl` hides this because it falls back to IPv4. |
159 | | - |
160 | | -6. **Pair the FE with the matching BE version.** The `dtq-dev` FE branch against |
161 | | - `dataquest/dspace:dspace-7_x` (DSpace 7.6.5) makes the FE's HAL parser recurse → |
162 | | - `RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Use the |
163 | | - branch's matching BE, `dataquest/dspace:dtq-dev-7.5` (DSpace 7.5). |
164 | | - |
165 | | -7. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent doesn't kill its |
166 | | - spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` (the |
167 | | - interactive prompt throws on a non-TTY). Kill the listener first: |
| 168 | + with no IPv4 fallback, but the BE publishes on `127.0.0.1` only, so Node fetches `ECONNREFUSED` |
| 169 | + → `undefined doesn't contain the link sites`. Worse, a host MISMATCH (FE on `127.0.0.1` but BE |
| 170 | + self-links on `localhost`) made the HAL parser resolve objects under two hostnames and recurse |
| 171 | + → `RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Keep |
| 172 | + **both** `DSPACE_REST_HOST` and `REST_URL` on `127.0.0.1`. (`curl` hides this — it falls back |
| 173 | + to IPv4.) |
| 174 | + |
| 175 | +6. **Match the BE to the FE version.** The FE reports **7.6.5**, so use a **7.6.5** BE |
| 176 | + (`dspace/dspace:dspace-7_x` or `dataquest/dspace:dspace-7_x`). The `dataquest/dspace:dtq-dev-7.5` |
| 177 | + image is **DSpace 7.5** and causes `An error occurred while retrieving the browse definitions` |
| 178 | + in the console. |
| 179 | + |
| 180 | +7. **Demo data ≠ empty repo, and Solr needs reindexing.** A fresh DB shows an empty homepage — |
| 181 | + that's expected, not a bug. Layer `db.entities.yml` (Option A) for sample content, then run |
| 182 | + `index-discovery -b` so browse/search/"What's New" populate. (The public |
| 183 | + `dspace7-entities-data.sql` now ships future 8.0/9.0/10.0 flyway entries; a 7.6.5 BE still |
| 184 | + reads the data fine via `migrate ignored`.) |
| 185 | + |
| 186 | +8. **Orphaned `ng serve` keeps port 4000.** Stopping `nodemon` doesn't kill its `ng serve` child, |
| 187 | + so the next start crashes on `Port 4000 is already in use`. Kill the listener first: |
168 | 188 | ```bash |
169 | 189 | pid=$(netstat -ano | grep LISTENING | grep ':4000' | awk '{print $NF}' | head -1) |
170 | | - taskkill //F //T //PID $pid # Git Bash needs the // flag form |
| 190 | + taskkill //F //T //PID $pid |
171 | 191 | ``` |
| 192 | + And do NOT switch git branches while `ng serve` runs — it watches the working tree and will |
| 193 | + recompile against the wrong files. Stop it first (or use a separate `git worktree`). |
172 | 194 |
|
173 | 195 | --- |
174 | 196 |
|
175 | 197 | ## Alternative: fully containerized FE (PR #1289's documented path) |
176 | 198 |
|
177 | | -To run the FE in Docker too (no native Node), use `docker/.env.local.example` as-is |
178 | | -(`DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up |
179 | | -**both** files with a local build: |
| 199 | +To run the FE in Docker too, use `docker/.env.local.example` as-is (`host.docker.internal`, |
| 200 | +`HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up **both** compose files with `--build`: |
180 | 201 | ```bash |
181 | 202 | docker compose --env-file docker/.env.local \ |
182 | 203 | -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build |
183 | 204 | # FE on http://localhost:4007 ; this build path exercises the Dockerfile cp fix from PR #1289. |
184 | 205 | ``` |
185 | 206 | `HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on all |
186 | | -interfaces — dev-only, not for an untrusted network. The native-FE recipe above avoids this. |
| 207 | +interfaces — dev-only, not for an untrusted network. |
0 commit comments