From e0652bbcf843df55716eaba3f87687cddaa0e5f0 Mon Sep 17 00:00:00 2001 From: boul2gom Date: Wed, 29 Jul 2026 15:03:49 +0200 Subject: [PATCH 1/2] docs: add custom reverse proxy setup guide for Community Edition --- docs/self-hosting/govern/reverse-proxy.md | 176 ++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/docs/self-hosting/govern/reverse-proxy.md b/docs/self-hosting/govern/reverse-proxy.md index fbc4046d..bf3dcd95 100644 --- a/docs/self-hosting/govern/reverse-proxy.md +++ b/docs/self-hosting/govern/reverse-proxy.md @@ -200,3 +200,179 @@ providers: ``` ::: + +::: details Community Edition + +This section provides configuration for setting up a custom external reverse proxy with Plane Community Edition. By default, Plane CE includes a bundled Caddy reverse proxy. + +### 1. Disable the bundled proxy + +To use your own reverse proxy, you must first disable the built-in proxy service. +Open your `docker-compose.yml` file and comment out or remove the `proxy` service at the end of the file. + +### 2. Connect to the Plane Docker network + +Your custom reverse proxy needs to resolve the internal container hostnames (like `web`, `api`, `admin`). The easiest way to achieve this is to run your reverse proxy container on the same Docker network as Plane (usually `plane-app_default`), or map all necessary ports from the individual containers to your host machine. + +### 3. Route mapping + +Plane CE relies on path-based routing to direct traffic to different services. You must map the following paths to their respective containers and internal ports: + +| Route Path | Destination Container | Internal Port | +| :--- | :--- | :--- | +| `/*` (catch-all) | `web` | 3000 | +| `/spaces/*` | `space` | 3000 | +| `/god-mode/*` | `admin` | 3000 | +| `/live/*` | `live` | 3000 | +| `/api/*`, `/auth/*`, `/static/*` | `api` | 8000 | +| `/${AWS_S3_BUCKET_NAME}/*` | `plane-minio` | 9000 | + +### 4. Configuration templates + +Below are example configurations for Nginx, Caddy, and Traefik assuming your proxy is on the same Docker network as the Plane containers. + +#### NGINX configuration + +```nginx +server { + listen 80; + server_name ; + + # File uploads (MinIO) + location ~ ^/uploads(/.*)?$ { + proxy_pass http://plane-minio:9000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # API, Auth, and Static + location ~ ^/(api|auth|static)(/.*)?$ { + proxy_pass http://api:8000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Live (WebSockets) + location /live/ { + proxy_pass http://live:3000; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # God mode (Admin) + location /god-mode/ { + proxy_pass http://admin:3000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Spaces + location /spaces/ { + proxy_pass http://space:3000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Frontend (Catch-all) + location / { + proxy_pass http://web:3000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + client_max_body_size 50M; +} +``` + +#### Caddy configuration + +```caddy + { + request_body { + max_size 50MB + } + + redir /spaces /spaces/ permanent + reverse_proxy /spaces/* space:3000 + + redir /god-mode /god-mode/ permanent + reverse_proxy /god-mode/* admin:3000 + + reverse_proxy /live/* live:3000 + + reverse_proxy /api/* api:8000 + reverse_proxy /auth/* api:8000 + reverse_proxy /static/* api:8000 + + # Replace 'uploads' with your actual bucket name if changed + reverse_proxy /uploads/* plane-minio:9000 + reverse_proxy /uploads plane-minio:9000 + + reverse_proxy /* web:3000 +} +``` + +#### Traefik configuration + +With Traefik, you can apply labels directly in your `docker-compose.yml` to the respective containers instead of a static configuration file. Assuming Traefik is set up to read Docker labels: + +```yaml +services: + web: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-web.rule=Host(``)" + - "traefik.http.services.plane-web.loadbalancer.server.port=3000" + + api: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-api.rule=Host(``) && (PathPrefix(`/api`) || PathPrefix(`/auth`) || PathPrefix(`/static`))" + - "traefik.http.services.plane-api.loadbalancer.server.port=8000" + + admin: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-admin.rule=Host(``) && PathPrefix(`/god-mode`)" + - "traefik.http.services.plane-admin.loadbalancer.server.port=3000" + + space: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-space.rule=Host(``) && PathPrefix(`/spaces`)" + - "traefik.http.services.plane-space.loadbalancer.server.port=3000" + + live: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-live.rule=Host(``) && PathPrefix(`/live`)" + - "traefik.http.services.plane-live.loadbalancer.server.port=3000" + + plane-minio: + # ... + labels: + - "traefik.enable=true" + - "traefik.http.routers.plane-minio.rule=Host(``) && PathPrefix(`/uploads`)" + - "traefik.http.services.plane-minio.loadbalancer.server.port=9000" +``` + +::: From d40f4fa6b3b2ad1519acec4ab6f51e09c9533620 Mon Sep 17 00:00:00 2001 From: boul2gom Date: Wed, 29 Jul 2026 22:21:09 +0200 Subject: [PATCH 2/2] docs: fix WebSocket and path boundaries in CE proxy templates --- docs/self-hosting/govern/reverse-proxy.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/self-hosting/govern/reverse-proxy.md b/docs/self-hosting/govern/reverse-proxy.md index bf3dcd95..b9261587 100644 --- a/docs/self-hosting/govern/reverse-proxy.md +++ b/docs/self-hosting/govern/reverse-proxy.md @@ -225,7 +225,7 @@ Plane CE relies on path-based routing to direct traffic to different services. Y | `/god-mode/*` | `admin` | 3000 | | `/live/*` | `live` | 3000 | | `/api/*`, `/auth/*`, `/static/*` | `api` | 8000 | -| `/${AWS_S3_BUCKET_NAME}/*` | `plane-minio` | 9000 | +| `//*` (default: `/uploads`) | `plane-minio` | 9000 | ### 4. Configuration templates @@ -239,6 +239,7 @@ server { server_name ; # File uploads (MinIO) + # Replace 'uploads' with your configured AWS_S3_BUCKET_NAME if changed location ~ ^/uploads(/.*)?$ { proxy_pass http://plane-minio:9000; proxy_set_header Host $http_host; @@ -259,6 +260,7 @@ server { # Live (WebSockets) location /live/ { proxy_pass http://live:3000; + proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_host; @@ -343,35 +345,35 @@ services: # ... labels: - "traefik.enable=true" - - "traefik.http.routers.plane-api.rule=Host(``) && (PathPrefix(`/api`) || PathPrefix(`/auth`) || PathPrefix(`/static`))" + - "traefik.http.routers.plane-api.rule=Host(``) && (Path(`/api`) || PathPrefix(`/api/`) || Path(`/auth`) || PathPrefix(`/auth/`) || Path(`/static`) || PathPrefix(`/static/`))" - "traefik.http.services.plane-api.loadbalancer.server.port=8000" admin: # ... labels: - "traefik.enable=true" - - "traefik.http.routers.plane-admin.rule=Host(``) && PathPrefix(`/god-mode`)" + - "traefik.http.routers.plane-admin.rule=Host(``) && (Path(`/god-mode`) || PathPrefix(`/god-mode/`))" - "traefik.http.services.plane-admin.loadbalancer.server.port=3000" space: # ... labels: - "traefik.enable=true" - - "traefik.http.routers.plane-space.rule=Host(``) && PathPrefix(`/spaces`)" + - "traefik.http.routers.plane-space.rule=Host(``) && (Path(`/spaces`) || PathPrefix(`/spaces/`))" - "traefik.http.services.plane-space.loadbalancer.server.port=3000" live: # ... labels: - "traefik.enable=true" - - "traefik.http.routers.plane-live.rule=Host(``) && PathPrefix(`/live`)" + - "traefik.http.routers.plane-live.rule=Host(``) && (Path(`/live`) || PathPrefix(`/live/`))" - "traefik.http.services.plane-live.loadbalancer.server.port=3000" plane-minio: # ... labels: - "traefik.enable=true" - - "traefik.http.routers.plane-minio.rule=Host(``) && PathPrefix(`/uploads`)" + - "traefik.http.routers.plane-minio.rule=Host(``) && (Path(`/uploads`) || PathPrefix(`/uploads/`))" - "traefik.http.services.plane-minio.loadbalancer.server.port=9000" ```