Skip to content

fix(Dockerfile): make combined image work behind reverse proxies — ARG VITE_API_BASE_URL + fix frontend-dist permissions#1581

Open
Yunaido wants to merge 2 commits intoevroon:masterfrom
Yunaido:master
Open

fix(Dockerfile): make combined image work behind reverse proxies — ARG VITE_API_BASE_URL + fix frontend-dist permissions#1581
Yunaido wants to merge 2 commits intoevroon:masterfrom
Yunaido:master

Conversation

@Yunaido
Copy link

@Yunaido Yunaido commented Mar 1, 2026

Problem

Two bugs in the combined Dockerfile (the one that builds the single image
serving both frontend and backend via SERVE_FRONTEND=true) make it broken
for any deployment where the browser doesn't access the server on localhost.

Bug 1: VITE_API_BASE_URL baked as http://localhost:8400/api

Vite replaces import.meta.env.* at compile time, not runtime. The
resulting JS bundle contains a literal http://localhost:8400/api string.
No Docker environment variable or runtime config can override it.

When a browser loads the app from any address other than localhost (e.g.
behind a reverse proxy, on a LAN IP, or a public domain), every API call
goes to the user's own machine and fails with ERR_CONNECTION_REFUSED.

This is the root cause of #1452, #1056, and the original #326.

Bug 2: frontend-dist files owned by root:root with 770 permissions

COPY --from=builder /app/dist /app/frontend-dist runs after USER bracket,
but Docker always runs COPY as root. Several files produced by the npm build
(all locale common.json files, favicons, SVG icons) have 770 permissions —
readable by owner/group only. Since the container runs as the bracket user
and the files are owned by root, these assets return ERR_HTTP2_PROTOCOL_ERROR,
breaking translations and icons.

Fix

Bug 1: Introduce a build ARG with /api as the default. This is the
correct value for SERVE_FRONTEND=true deployments (same-origin, so /api
resolves against whatever the browser's current origin is). Users who want
a different value can override it with --build-arg VITE_API_BASE_URL=...
at build time.

Bug 2: Add --chown=bracket:bracket to the COPY --from=builder
instruction so all frontend-dist files are owned by the app user.

Changes

 COPY frontend .
 
+ARG VITE_API_BASE_URL=/api
+
 RUN apk add pnpm && \
     CI=true pnpm install && \
-    VITE_API_BASE_URL=http://localhost:8400/api pnpm build
+    VITE_API_BASE_URL=${VITE_API_BASE_URL} pnpm build
-COPY --from=builder /app/dist /app/frontend-dist
+COPY --from=builder --chown=bracket:bracket /app/dist /app/frontend-dist

Testing

Tested on TrueNAS SCALE (ZFS) behind Traefik with SERVE_FRONTEND=true and
API_PREFIX=/api. Before this fix: login failed, locales and favicons returned
protocol errors. After: all API calls resolve correctly, all assets load.

Fixes #1452
Fixes #1056

@Yunaido Yunaido changed the title Fix: Combined image VITE_API_BASE_URL baked as localhost + frontend-d… fix(Dockerfile): make combined image work behind reverse proxies — ARG VITE_API_BASE_URL + fix frontend-dist permissions Mar 1, 2026
@Yunaido Yunaido marked this pull request as ready for review March 1, 2026 17:41
@FrederikRogalski
Copy link

Ran into the exact same issue deploying behind Traefik with SERVE_FRONTEND=true. The combined image has localhost:8400 baked into the JS bundle at build time, so every API call fails in the browser with ERR_CONNECTION_REFUSED. Workaround is to use the separate bracket-frontend + bracket-backend images, but the fix in this PR (defaulting VITE_API_BASE_URL to /api) is the right solution for the combined image. Please merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internal server error Environment variable not being set correctly

2 participants