Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds configurable app base-path support (VITE_WEB_BASE_PATH / APP_BASE_PATH), wires base-path into Vite, router basenames, manifests and Docker build args, normalizes manifest icon paths to relative, replaces WEB_BASE_URL → WEB_URL usages, adds client-side mount guards for hydration, and provides a subpath docker-compose for Traefik routing. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Traefik
participant AppService
participant StaticFiles
Client->>Traefik: GET /plane/_next/static/...
Traefik->>Traefik: StripPrefix /plane
Traefik->>AppService: GET /_next/static/...
AppService->>StaticFiles: Resolve asset via joinUrlPath(WEB_BASE_PATH, assetPath)
StaticFiles-->>AppService: Return asset bytes
AppService-->>Traefik: 200 + Asset
Traefik-->>Client: 200 + Asset
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/admin/app/`(all)/(dashboard)/workspace/create/form.tsx:
- Line 40: The current line double-encodes WEB_URL and uses a fallback that
omits the app base path; update the workspaceBaseURL logic: stop calling
encodeURI on WEB_URL (use WEB_URL as-is) and build a fallback that preserves the
origin plus the app base path from window.location.pathname (e.g., reconstruct
origin + first path segment(s) so the base path isn't lost) when WEB_URL is
undefined; locate and change the workspaceBaseURL constant in form.tsx
(references: workspaceBaseURL, WEB_URL, window.location).
In `@apps/web/app/layout.tsx`:
- Line 63: The head contains two <link rel="manifest"> tags pointing to
different files (site.webmanifest.json and manifest.json) so the browser only
uses the first; pick one approach: either remove the duplicate link (delete the
extra <link rel="manifest"> that uses joinUrlPath(WEB_BASE_PATH, "...")
referencing the manifest you don't want) or merge the two manifest files into a
single consolidated manifest (combine icon entries and other fields, save as a
single filename) and update the remaining <link rel="manifest"> (the usage that
calls joinUrlPath and WEB_BASE_PATH) to point to the consolidated manifest;
ensure icon paths inside the chosen manifest are correct for the deployed
assets.
🧹 Nitpick comments (2)
turbo.json (1)
9-9: Addition ofBASE_URLto globalEnv looks correct.This ensures Turbo properly invalidates the build cache when the base URL configuration changes, which is necessary for the custom base path feature.
Nitpick: For consistency, consider placing
BASE_URLin alphabetical order (afterAPP_VERSION, beforeDEV)."globalEnv": [ "APP_VERSION", + "BASE_URL", "DEV", "LOG_LEVEL", "NODE_ENV", - "BASE_URL", "SENTRY_DSN",apps/web/public/site.webmanifest.json (1)
10-19: LGTM on relative paths. Consider providing properly sized icons.The switch to relative paths (
plane-logos/plane-mobile-pwa.png) is correct for base path support.However, both icon entries reference the same image file with different declared sizes (192x192 and 512x512). For optimal PWA quality, consider providing separate image files at each resolution to avoid browser scaling artifacts.
ea2077c to
da879a3
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/admin/nginx/nginx.conf (1)
29-33:⚠️ Potential issue | 🟠 MajorHealthcheck will fail — no location handles
/The Dockerfile.admin (Line 87) defines a healthcheck that curls
http://127.0.0.1:3000/, but the only location block here is/god-mode/. Requests to/will return a 404, causing the container to be perpetually marked unhealthy.Additionally,
location /god-mode/(with trailing slash) won't match a bare/god-moderequest (without trailing slash), which could cause a redirect loop or 404 depending on the client.Consider adding a catch-all or explicit health endpoint, and handling the no-trailing-slash case:
Proposed fix
- location /god-mode/ { + location /god-mode { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /god-mode/index.html; } + + location = /healthz { + return 200 'ok'; + add_header Content-Type text/plain; + }Then update the Dockerfile healthcheck to target
/healthz:- CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1 + CMD curl -fsS http://127.0.0.1:3000/healthz >/dev/null || exit 1🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin/nginx/nginx.conf` around lines 29 - 33, Add a root/catch-all location and an explicit health endpoint so the Docker healthcheck (which currently curls the container root) succeeds, and make `/god-mode` without trailing slash redirect or serve the same content as `/god-mode/`; specifically, update the nginx config to (1) add a location /healthz that returns 200 for health checks, (2) add a location / (or a catch-all) that serves the app (or proxies) so requests to `/` don't 404, and (3) ensure the existing location /god-mode/ also handles the no-trailing-slash case by adding a redirect or a `location = /god-mode { ... }` that serves the same index; then update Dockerfile.admin healthcheck to curl /healthz.
🧹 Nitpick comments (4)
docker-compose-subpath.yml (3)
75-80:spaceservice lacksstripprefixmiddleware — intentional but inconsistent withadmin.The
spaceservice forwards/plane/spaces/...without stripping, relying on SSR withbasename="/plane/spaces"to handle full paths. This is correct for SSR. However, theadminservice also defines its own base path (/plane/god-mode) but usesstripprefixto remove/plane. This asymmetry is fine given the different architectures (SSR vs static nginx), but documenting the rationale in comments would help future maintainers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose-subpath.yml` around lines 75 - 80, The space service's Traefik labels (traefik.http.routers.plane-space.rule and related labels) intentionally omit a stripprefix middleware while the admin service uses stripprefix; add a concise comment above the space service labels explaining that space is an SSR app using basename="/plane/spaces" so it must receive the full path (hence no stripprefix), and reference that the admin service is static/nginx and strips /plane for routing—this documents the asymmetry for future maintainers.
239-241: Externaltraefik_defaultnetwork requires a pre-existing Traefik instance.This compose file won't start successfully without a Traefik container already running and creating the
traefik_defaultnetwork. Consider adding a brief comment or a companiondocker-compose-traefik.ymlreference so users know how to bootstrap the required Traefik infrastructure.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose-subpath.yml` around lines 239 - 241, The Compose file declares an external network "traefik_default" (networks -> traefik_default -> external: true) which requires a pre-existing Traefik stack to create that network; add a brief inline comment above the networks block explaining that Traefik must be deployed first (or reference a companion compose like docker-compose-traefik.yml), or alternatively provide an optional local network fallback by removing external: true or documenting how to bootstrap the traefik_default network with docker-compose up for the Traefik stack.
186-188: Hardcoded default credentials — document as development-only.The Postgres (
plane/plane), RabbitMQ (plane/plane), and MinIO (access-key/secret-key) credentials are fine for a local development/demo compose file, but add a comment at the top of the file (or in a README) warning that these must be changed for production deployments.Also applies to: 205-207, 221-222
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose-subpath.yml` around lines 186 - 188, Add a clear development-only warning comment at the top of the compose file (and/or add a README note) stating that hardcoded credentials must be changed for production; specifically call out the Postgres env vars POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB and the RabbitMQ and MinIO default credentials (e.g., plane/plane and access-key/secret-key) so operators know these are insecure for production and must be replaced or injected via secrets/ENV management.apps/admin/react-router.config.ts (1)
4-4: IdenticalbasePathexpression duplicated across three router configs.This exact expression appears in
apps/web/react-router.config.ts,apps/space/react-router.config.ts, and here. Consider extracting a shared helper (e.g.,resolveBasePath(envVar: string)in@plane/utils) to keep them in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin/react-router.config.ts` at line 4, The duplicate complex basePath computation (const basePath = (joinUrlPath(process.env.VITE_ADMIN_BASE_PATH ?? "") ?? "/").replace(/\/$/, "") || "/";) is repeated across three router configs; extract it to a shared helper like resolveBasePath(envVar: string) in `@plane/utils` and use that helper from each config. Implement resolveBasePath to accept the env var name or value, call joinUrlPath internally, ensure trailing slash trimming (.replace(/\/$/, "")) and fallback to "/" exactly as the current logic does, then replace the inline basePath declarations in apps/admin/react-router.config.ts (and the other router configs) with a call to resolveBasePath(process.env.VITE_ADMIN_BASE_PATH) so all configs stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/admin/Dockerfile.admin`:
- Line 38: The Dockerfile's build ARG VITE_ADMIN_BASE_PATH was changed to
"/plane/god-mode", breaking the previous default; revert ARG
VITE_ADMIN_BASE_PATH back to "/god-mode" so existing deployments stay
compatible, and keep docker-compose-subpath (which already overrides
VITE_ADMIN_BASE_PATH) to provide the subpath "/plane/god-mode" for those
deployments; update the ARG declaration (VITE_ADMIN_BASE_PATH) to the original
default and ensure documentation/comments note that docker-compose-subpath.yml
supplies the subpath override.
In `@docker-compose-subpath.yml`:
- Around line 158-175: The live service is forwarding requests at /plane/live
but the app expects env.LIVE_BASE_PATH (default /live); fix by either adding a
Traefik StripPrefix middleware and attaching it to the router or by configuring
the app to accept /plane/live via env: for example create a middleware name like
plane-live-strip using
traefik.http.middlewares.plane-live-strip.stripprefix.prefixes=/plane then add
traefik.http.routers.plane-live.middlewares=plane-live-strip to the live service
labels, or set LIVE_BASE_PATH="/plane/live" in the live env_file to align the
app’s router with the forwarded path (reference symbols: service name "live",
env var "LIVE_BASE_PATH", label keys
"traefik.http.middlewares.*.stripprefix.prefixes" and
"traefik.http.routers.plane-live.middlewares").
---
Outside diff comments:
In `@apps/admin/nginx/nginx.conf`:
- Around line 29-33: Add a root/catch-all location and an explicit health
endpoint so the Docker healthcheck (which currently curls the container root)
succeeds, and make `/god-mode` without trailing slash redirect or serve the same
content as `/god-mode/`; specifically, update the nginx config to (1) add a
location /healthz that returns 200 for health checks, (2) add a location / (or a
catch-all) that serves the app (or proxies) so requests to `/` don't 404, and
(3) ensure the existing location /god-mode/ also handles the no-trailing-slash
case by adding a redirect or a `location = /god-mode { ... }` that serves the
same index; then update Dockerfile.admin healthcheck to curl /healthz.
---
Duplicate comments:
In `@apps/admin/Dockerfile.admin`:
- Around line 86-87: The HEALTHCHECK in Dockerfile.admin uses CMD curl -fsS
http://127.0.0.1:3000/ which will always fail because nginx only serves under
/god-mode/; update the HEALTHCHECK command to target the correct path (e.g.,
http://127.0.0.1:3000/god-mode/) so the container health probe hits the served
endpoint; modify the HEALTHCHECK line in Dockerfile.admin accordingly and keep
the same flags (--interval, --timeout, --start-period, --retries) while changing
only the URL path.
---
Nitpick comments:
In `@apps/admin/react-router.config.ts`:
- Line 4: The duplicate complex basePath computation (const basePath =
(joinUrlPath(process.env.VITE_ADMIN_BASE_PATH ?? "") ?? "/").replace(/\/$/, "")
|| "/";) is repeated across three router configs; extract it to a shared helper
like resolveBasePath(envVar: string) in `@plane/utils` and use that helper from
each config. Implement resolveBasePath to accept the env var name or value, call
joinUrlPath internally, ensure trailing slash trimming (.replace(/\/$/, "")) and
fallback to "/" exactly as the current logic does, then replace the inline
basePath declarations in apps/admin/react-router.config.ts (and the other router
configs) with a call to resolveBasePath(process.env.VITE_ADMIN_BASE_PATH) so all
configs stay in sync.
In `@docker-compose-subpath.yml`:
- Around line 75-80: The space service's Traefik labels
(traefik.http.routers.plane-space.rule and related labels) intentionally omit a
stripprefix middleware while the admin service uses stripprefix; add a concise
comment above the space service labels explaining that space is an SSR app using
basename="/plane/spaces" so it must receive the full path (hence no
stripprefix), and reference that the admin service is static/nginx and strips
/plane for routing—this documents the asymmetry for future maintainers.
- Around line 239-241: The Compose file declares an external network
"traefik_default" (networks -> traefik_default -> external: true) which requires
a pre-existing Traefik stack to create that network; add a brief inline comment
above the networks block explaining that Traefik must be deployed first (or
reference a companion compose like docker-compose-traefik.yml), or alternatively
provide an optional local network fallback by removing external: true or
documenting how to bootstrap the traefik_default network with docker-compose up
for the Traefik stack.
- Around line 186-188: Add a clear development-only warning comment at the top
of the compose file (and/or add a README note) stating that hardcoded
credentials must be changed for production; specifically call out the Postgres
env vars POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB and the RabbitMQ and
MinIO default credentials (e.g., plane/plane and access-key/secret-key) so
operators know these are insecure for production and must be replaced or
injected via secrets/ENV management.
| ARG VITE_ADMIN_BASE_URL="" | ||
| ENV VITE_ADMIN_BASE_URL=$VITE_ADMIN_BASE_URL | ||
| ARG VITE_ADMIN_BASE_PATH="/god-mode" | ||
| ARG VITE_ADMIN_BASE_PATH="/plane/god-mode" |
There was a problem hiding this comment.
Breaking default: VITE_ADMIN_BASE_PATH changed from /god-mode to /plane/god-mode
The PR states backward compatibility is preserved, but changing this default means existing deployments that rely on the previous /god-mode default will break without explicitly overriding the build arg. Consider keeping the original default and letting subpath deployments override it:
-ARG VITE_ADMIN_BASE_PATH="/plane/god-mode"
+ARG VITE_ADMIN_BASE_PATH="/god-mode"The docker-compose-subpath.yml already passes VITE_ADMIN_BASE_PATH: "/plane/god-mode" explicitly (Line 13), so subpath deployments are unaffected.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ARG VITE_ADMIN_BASE_PATH="/plane/god-mode" | |
| ARG VITE_ADMIN_BASE_PATH="/god-mode" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/admin/Dockerfile.admin` at line 38, The Dockerfile's build ARG
VITE_ADMIN_BASE_PATH was changed to "/plane/god-mode", breaking the previous
default; revert ARG VITE_ADMIN_BASE_PATH back to "/god-mode" so existing
deployments stay compatible, and keep docker-compose-subpath (which already
overrides VITE_ADMIN_BASE_PATH) to provide the subpath "/plane/god-mode" for
those deployments; update the ARG declaration (VITE_ADMIN_BASE_PATH) to the
original default and ensure documentation/comments note that
docker-compose-subpath.yml supplies the subpath override.
| live: | ||
| build: | ||
| context: . | ||
| dockerfile: ./apps/live/Dockerfile.live | ||
| args: | ||
| DOCKER_BUILDKIT: 1 | ||
| restart: unless-stopped | ||
| networks: | ||
| - traefik_default | ||
| - plane_backend | ||
| env_file: | ||
| - ./apps/live/.env | ||
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.plane-live.rule=PathPrefix(`/plane/live`)" | ||
| - "traefik.http.routers.plane-live.priority=90" | ||
| - "traefik.http.routers.plane-live.entrypoints=web" | ||
| - "traefik.http.services.plane-live.loadbalancer.server.port=3000" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and inspect the live service configuration files
echo "=== Checking apps/live structure ==="
find apps/live -type f \( -name "*.ts" -o -name "*.js" -o -name "*.conf" -o -name "Dockerfile*" \) | head -20
echo -e "\n=== Checking Dockerfile ==="
fd -t f 'Dockerfile' apps/live/ -x cat {}
echo -e "\n=== Looking for routing/server setup in source files ==="
fd -t f '\.(ts|js)$' apps/live/src/ -x grep -l 'listen\|port\|path\|prefix\|basePath\|router' {} \; | head -10
echo -e "\n=== Checking for base path or prefix configuration ==="
rg -i 'base.*path|prefix|router.*path' apps/live/src/ -A2 -B2 --type ts 2>/dev/null | head -40Repository: makeplane/plane
Length of output: 6998
🏁 Script executed:
#!/bin/bash
# Check the server setup and HTTP routing configuration
echo "=== Checking server.ts (HTTP server setup) ==="
cat apps/live/src/server.ts
echo -e "\n=== Checking start.ts (application startup) ==="
cat apps/live/src/start.ts
echo -e "\n=== Checking hocuspocus.ts (WebSocket routing) ==="
head -50 apps/live/src/hocuspocus.tsRepository: makeplane/plane
Length of output: 7078
🏁 Script executed:
#!/bin/bash
# Check the environment configuration for LIVE_BASE_PATH
echo "=== Checking env.ts for LIVE_BASE_PATH ==="
cat apps/live/src/env.tsRepository: makeplane/plane
Length of output: 1403
Add stripprefix middleware or configure LIVE_BASE_PATH for the live service.
The live service mounts its Express router at env.LIVE_BASE_PATH (default: /live), but Traefik routes /plane/live without a stripprefix middleware. This causes a path mismatch: requests at /plane/live/ws are forwarded to the container as-is, but the app expects /live/ws. Either add a stripprefix middleware to remove /plane (like web, admin, and api services have), or set LIVE_BASE_PATH="/plane/live" via environment configuration.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose-subpath.yml` around lines 158 - 175, The live service is
forwarding requests at /plane/live but the app expects env.LIVE_BASE_PATH
(default /live); fix by either adding a Traefik StripPrefix middleware and
attaching it to the router or by configuring the app to accept /plane/live via
env: for example create a middleware name like plane-live-strip using
traefik.http.middlewares.plane-live-strip.stripprefix.prefixes=/plane then add
traefik.http.routers.plane-live.middlewares=plane-live-strip to the live service
labels, or set LIVE_BASE_PATH="/plane/live" in the live env_file to align the
app’s router with the forwarded path (reference symbols: service name "live",
env var "LIVE_BASE_PATH", label keys
"traefik.http.middlewares.*.stripprefix.prefixes" and
"traefik.http.routers.plane-live.middlewares").
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
apps/admin/nginx/nginx.conf (2)
33-42: Hardcoded/god-modepaths vs. configurableVITE_ADMIN_BASE_PATH.The PR introduces a configurable
VITE_ADMIN_BASE_PATH(defaulting to/plane/god-modein the Dockerfile), but this nginx config hardcodes/god-modein all location blocks. This works when the reverse proxy (Traefik) strips the outer prefix before forwarding, but if the deployment topology changes or someone customizes the admin base path without a corresponding proxy rewrite, these routes will break silently.Consider either documenting this coupling or templating the nginx config (e.g., via
envsubstat container start) to stay in sync withVITE_ADMIN_BASE_PATH.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin/nginx/nginx.conf` around lines 33 - 42, The nginx config hardcodes the /god-mode paths in the location blocks (the exact "location = /god-mode" and "location /god-mode/" entries) while the app uses a configurable VITE_ADMIN_BASE_PATH, causing mismatches; update the nginx config to derive the base path from the VITE_ADMIN_BASE_PATH environment variable (for example by templating the two location blocks and replacing /god-mode with the env var at container start via envsubst or another templating step) or add a clear comment/doc explaining the required proxy rewrite so deployments that change VITE_ADMIN_BASE_PATH remain consistent with the location rules.
29-31: Health check response will haveContent-Type: application/octet-stream.The
default_typeat thehttplevel (line 10) isapplication/octet-stream, so the health probe response body"healthy\n"will be served with that content type. While most health-check consumers only inspect the status code, it's cleaner to set it explicitly.Suggested fix
location = / { + default_type text/plain; return 200 "healthy\n"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin/nginx/nginx.conf` around lines 29 - 31, The health-check location block (location = /) currently returns a plain string but inherits default_type application/octet-stream from the http context; update the location block to explicitly set the response content type (e.g. add default_type text/plain; or add_header Content-Type "text/plain; charset=utf-8" always;) so the body "healthy\n" is served with text/plain; modify the location = / block accordingly.docker-compose-subpath.yml (2)
211-213: Pin the MinIO image version for reproducibility.All other infrastructure images are pinned (
postgres:15.7-alpine,valkey:7.2.11-alpine,rabbitmq:3.13.6-management-alpine), butminio/miniodefaults tolatest. This can cause unexpected breakage on rebuild.♻️ Proposed fix
- image: minio/minio + image: minio/minio:RELEASE.2024-06-13T22-53-53ZPick a suitable stable release tag.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose-subpath.yml` around lines 211 - 213, The MinIO service "plane-minio" currently uses the unpinned image "minio/minio" which may pull latest; update the image field for the plane-minio service to a specific stable MinIO release tag (e.g., replace "minio/minio" with "minio/minio:<stable-tag>") to ensure reproducible builds and avoid accidental upgrades—choose an appropriate stable tag consistent with other pinned images.
2-3: Inconsistentcontainer_nameusage across services.
plane-adminspecifiescontainer_name: plane-admin-subpathbut no other service does. This can cause confusion when inspecting containers. Consider either addingcontainer_nameto all services or removing it fromplane-adminfor consistency.Also applies to: 30-31
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose-subpath.yml` around lines 2 - 3, The docker-compose service "plane-admin" sets container_name: plane-admin-subpath while other services do not, causing inconsistency; either remove the container_name line from the "plane-admin" service to let Docker auto-name containers, or add consistent container_name entries for every service (e.g., <service>-subpath) so naming is uniform—locate the "plane-admin" service block and the other service blocks in docker-compose-subpath.yml and apply one consistent approach across all services.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docker-compose-subpath.yml`:
- Around line 186-188: Replace hardcoded credentials by using environment
variable substitution with sensible defaults and add a top-of-file warning;
specifically change POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB to use
${POSTGRES_USER:-plane}, ${POSTGRES_PASSWORD:-plane}, ${POSTGRES_DB:-plane} and
apply the same pattern to the plane-mq and plane-minio credentials (e.g.,
replace literal access-key/secret-key with ${MINIO_ACCESS_KEY:-access-key} /
${MINIO_SECRET_KEY:-secret-key} or analogous MQ vars), and add a prominent
comment at the top of the compose file instructing users to override these vars
for production.
- Around line 75-80: The plane-space router is missing the Traefik stripPrefix
middleware so requests keep the leading /plane and 404; add a middleware label
(e.g., traefik.http.middlewares.plane-strip.stripprefix.prefixes=/plane) and
attach it to the router via
traefik.http.routers.plane-space.middlewares=plane-strip@docker (use the same
middleware name used by admin/web if one exists) so the container receives paths
starting with /spaces.
---
Duplicate comments:
In `@docker-compose-subpath.yml`:
- Around line 158-175: The live service is missing the Traefik stripprefix
middleware so requests come in as /plane/live/... but the app expects /live/...;
either add the same stripprefix middleware labels used for the space service
(create/attach a middleware that strips the `/plane` prefix and reference it in
the live service labels, e.g. traefik.http.routers.plane-live.middlewares=...)
or set LIVE_BASE_PATH="/plane/live" in the live service environment (.env) so
Express receives the expected base path; update the service named "live" and
ensure the middleware name matches the router label used by plane-live.
---
Nitpick comments:
In `@apps/admin/nginx/nginx.conf`:
- Around line 33-42: The nginx config hardcodes the /god-mode paths in the
location blocks (the exact "location = /god-mode" and "location /god-mode/"
entries) while the app uses a configurable VITE_ADMIN_BASE_PATH, causing
mismatches; update the nginx config to derive the base path from the
VITE_ADMIN_BASE_PATH environment variable (for example by templating the two
location blocks and replacing /god-mode with the env var at container start via
envsubst or another templating step) or add a clear comment/doc explaining the
required proxy rewrite so deployments that change VITE_ADMIN_BASE_PATH remain
consistent with the location rules.
- Around line 29-31: The health-check location block (location = /) currently
returns a plain string but inherits default_type application/octet-stream from
the http context; update the location block to explicitly set the response
content type (e.g. add default_type text/plain; or add_header Content-Type
"text/plain; charset=utf-8" always;) so the body "healthy\n" is served with
text/plain; modify the location = / block accordingly.
In `@docker-compose-subpath.yml`:
- Around line 211-213: The MinIO service "plane-minio" currently uses the
unpinned image "minio/minio" which may pull latest; update the image field for
the plane-minio service to a specific stable MinIO release tag (e.g., replace
"minio/minio" with "minio/minio:<stable-tag>") to ensure reproducible builds and
avoid accidental upgrades—choose an appropriate stable tag consistent with other
pinned images.
- Around line 2-3: The docker-compose service "plane-admin" sets container_name:
plane-admin-subpath while other services do not, causing inconsistency; either
remove the container_name line from the "plane-admin" service to let Docker
auto-name containers, or add consistent container_name entries for every service
(e.g., <service>-subpath) so naming is uniform—locate the "plane-admin" service
block and the other service blocks in docker-compose-subpath.yml and apply one
consistent approach across all services.
| labels: | ||
| - "traefik.enable=true" | ||
| - "traefik.http.routers.plane-space.rule=PathPrefix(`/plane/spaces`)" | ||
| - "traefik.http.routers.plane-space.priority=80" | ||
| - "traefik.http.routers.plane-space.entrypoints=web" | ||
| - "traefik.http.services.plane-space.loadbalancer.server.port=3000" |
There was a problem hiding this comment.
Missing stripprefix middleware on the space service will cause routing failures.
Every other Traefik-routed frontend service (admin, web) defines a stripprefix middleware to remove /plane before forwarding to the container. The space service is missing both the middleware assignment and its definition, so requests will arrive at the container with the full /plane/spaces/... path instead of /spaces/..., causing 404s.
🐛 Proposed fix — add stripprefix middleware for `space`
labels:
- "traefik.enable=true"
- "traefik.http.routers.plane-space.rule=PathPrefix(`/plane/spaces`)"
- "traefik.http.routers.plane-space.priority=80"
- "traefik.http.routers.plane-space.entrypoints=web"
+ - "traefik.http.routers.plane-space.middlewares=plane-space-stripprefix"
- "traefik.http.services.plane-space.loadbalancer.server.port=3000"
+ - "traefik.http.middlewares.plane-space-stripprefix.stripprefix.prefixes=/plane"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose-subpath.yml` around lines 75 - 80, The plane-space router is
missing the Traefik stripPrefix middleware so requests keep the leading /plane
and 404; add a middleware label (e.g.,
traefik.http.middlewares.plane-strip.stripprefix.prefixes=/plane) and attach it
to the router via
traefik.http.routers.plane-space.middlewares=plane-strip@docker (use the same
middleware name used by admin/web if one exists) so the container receives paths
starting with /spaces.
| POSTGRES_USER: plane | ||
| POSTGRES_PASSWORD: plane | ||
| POSTGRES_DB: plane |
There was a problem hiding this comment.
Hardcoded plaintext credentials across database, message queue, and object storage services.
While this is an example/reference compose file, the hardcoded credentials (plane/plane, access-key/secret-key) could be copy-pasted into production deployments. Consider adding a prominent comment at the top of the file warning users to change credentials, or use environment variable substitution (${POSTGRES_PASSWORD:-plane}) so users can override without editing the file.
🛡️ Suggested approach — use env var substitution with defaults
environment:
- POSTGRES_USER: plane
- POSTGRES_PASSWORD: plane
- POSTGRES_DB: plane
+ POSTGRES_USER: ${POSTGRES_USER:-plane}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-plane}
+ POSTGRES_DB: ${POSTGRES_DB:-plane}Apply the same pattern to plane-mq and plane-minio credentials.
Also applies to: 204-207, 221-222
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose-subpath.yml` around lines 186 - 188, Replace hardcoded
credentials by using environment variable substitution with sensible defaults
and add a top-of-file warning; specifically change
POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB to use ${POSTGRES_USER:-plane},
${POSTGRES_PASSWORD:-plane}, ${POSTGRES_DB:-plane} and apply the same pattern to
the plane-mq and plane-minio credentials (e.g., replace literal
access-key/secret-key with ${MINIO_ACCESS_KEY:-access-key} /
${MINIO_SECRET_KEY:-secret-key} or analogous MQ vars), and add a prominent
comment at the top of the compose file instructing users to override these vars
for production.
f78d7fa to
b14d4dd
Compare
|
Hi team, this PR adds full subpath deployment support (fixes #5200). Would love a review when you get a chance! cc @pushya22 @SatishGandham |
- Add VITE_WEB_BASE_PATH configuration to vite.config.ts and Dockerfile.web - Configure React Router basename with trailing slash strip - Fix hardcoded manifest paths in root.tsx and layout.tsx to use joinUrlPath - Fix hydration mismatch in HydrateFallback component - Update manifest.json and site.webmanifest.json to use relative icon paths - Update FileUploadService to use API_BASE_URL for correct upload origin - Document VITE_WEB_BASE_PATH in .env.example Fixes makeplane#5200
- Fix manifest paths in root.tsx to use joinUrlPath with base path - Fix hydration mismatch in HydrateFallback component - Strip trailing slash from React Router basename - Add Nginx health check endpoint (location = /) for Docker healthcheck - Add exact /god-mode location match to prevent Nginx 301 redirects - Update sidebar help section to use WEB_URL for Plane redirect - Fix workspace form URL preview to include base path - Fix workspace list item links to use WEB_URL - Update site.webmanifest.json to use relative icon paths - Add VITE_ADMIN_BASE_PATH build arg to Dockerfile - Document configuration in .env.example
- Fix hydration mismatch in HydrateFallback component - Strip trailing slash from React Router basename - Update site.webmanifest.json to use relative icon paths - Document VITE_SPACE_BASE_PATH in .env.example
- Add APP_BASE_PATH formatting logic to base_host() in plane/utils/host.py - Apply same fix to plane/authentication/utils/host.py (duplicate utility) - Add APP_BASE_PATH to S3 endpoint URL in storage.py for correct presigned URLs - Ensures login/logout redirects and file uploads preserve the subpath prefix - Document APP_BASE_PATH in .env.example
- Add docker-compose-subpath.yml with full Traefik-based subpath configuration - Configure routing priorities for admin (100), api (100), live (90), space (80), web (50) - Add specific Traefik router for /plane/uploads to fix mixed content S3 uploads - Add BASE_URL to turbo.json globalEnv for build-time env passthrough - Update live app package.json dependency
b14d4dd to
e78e322
Compare
Fix: Add custom base path support for subpath deployments
Description
Fixes #5200
This PR adds comprehensive support for deploying Plane on custom base paths (e.g.,
mydomain.com/plane), enabling reverse proxy deployments on subpaths. Previously, static assets, internal links, and authentication redirects failed when deploying on a subpath.Problem
When users deploy Plane on a subpath using reverse proxies, multiple issues occurred:
The
spaceandadminapps already supported base paths viaVITE_SPACE_BASE_PATHandVITE_ADMIN_BASE_PATH, but the web app was missing this feature, cross-app navigation was broken, and backend auth redirects ignoredAPP_BASE_PATH.Solution
1. Web App Base Path Support
Added
VITE_WEB_BASE_PATHconfiguration following the same pattern as other apps:VITE_WEB_BASE_PATHbuild argumentbasenamewith trailing slash strip in react-router.config.ts2. Manifest Path Fixes
Fixed hardcoded manifest paths across all apps to support subpaths:
/)3. Hydration Fixes
Resolved React hydration mismatches in HydrateFallback components:
admin, andspaceapps4. Admin App Cross-Navigation
Fixed admin app links to respect web app's custom base path:
WEB_URL(includes base path)5. Backend Auth Redirect Fix
Fixed base_host() in plane/utils/host.py and plane/authentication/utils/host.py to append
APP_BASE_PATHwhen generating redirect URLs. Previously, the function formatted and appended paths forADMIN_BASE_PATHandSPACE_BASE_PATHbut completely skippedAPP_BASE_PATH, causing login/logout to redirect to root domain without the subpath prefix.6. Admin Nginx Configuration
location = /) returning 200 to satisfy Docker'sHEALTHCHECK. Without this, the container was marked unhealthy and Traefik removed it from its routing pool entirely.location = /god-modematch to serveindex.htmldirectly, preventing Nginx's default 301 trailing-slash redirect which broke the proxy chain.7. MinIO Upload Fixes (Mixed Content & Subpath)
APP_BASE_PATHtoS3Storageendpoint URL in storage.py to ensure presigned URLs point to the correct subpath.FileUploadServiceto useAPI_BASE_URLensuring the upload request origin matches the application origin./plane/uploadsin docker-compose-subpath.yml with TLS support to fix mixed-content blocks and route directly to MinIO.8. Subpath Deployment Example
Added docker-compose-subpath.yml with a complete Traefik-based routing configuration:
stripprefixmiddleware configuration (strip only/plane, not the full subpath)8. Configuration & Documentation
BASE_URLtoturbo.jsonglobalEnv for lintingVITE_WEB_BASE_PATHdocumentationAPP_BASE_PATHconfigurationChanges
Core Files Modified
Web App:
Admin App:
/god-modematchSpace App:
Backend API:
APP_BASE_PATHsupport in base_host()APP_BASE_PATHto S3 endpoint URLAPP_BASE_PATHInfrastructure:
turbo.json—BASE_URLin globalEnvTesting
Quick Start (Subpath Deployment):
Verification:
/plane/assets/.../plane/site.webmanifest.json/plane//plane/prefix/plane/prefix/plane/god-modeserves admin app without redirect loopsBackward Compatibility
✅ Not a breaking change
VITE_WEB_BASE_PATHis/(root path)APP_BASE_PATHis/(root path)Checklist
Related