From 3ed34c67d2051485b987400e8057f2978ab27bfa Mon Sep 17 00:00:00 2001 From: Siva Subramanian Date: Tue, 17 Feb 2026 22:56:30 +0100 Subject: [PATCH 1/2] feat(deploy): add single-domain routing and path-based app endpoints --- .gitignore | 8 +++- apps/public/config.json | 10 ++-- apps/src/services/Services.js | 11 +++-- apps/src/views/Home.vue | 2 +- docker-compose.yaml | 88 +++++++++++++++++------------------ 5 files changed, 62 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index 798b2f9..4a45aac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ -websocket \ No newline at end of file +websocket + +# Local runtime artifacts +mongo-data/ +app-data/ +letsencrypt/ +.env diff --git a/apps/public/config.json b/apps/public/config.json index ef1f760..1f532c0 100644 --- a/apps/public/config.json +++ b/apps/public/config.json @@ -1,6 +1,6 @@ { - "api_URL": "https://api-os.flowrabbit.ai", - "proxy_URL": "https://proxy-os.flowrabbit.ai", - "app_URL": "http://localhost:8081", - "node_URL": "https://node-os.flowrabbit.ai" -} \ No newline at end of file + "api_URL": "/api", + "proxy_URL": "", + "app_URL": "/apps", + "node_URL": "/docs" +} diff --git a/apps/src/services/Services.js b/apps/src/services/Services.js index 1cfbff8..f6970b6 100644 --- a/apps/src/services/Services.js +++ b/apps/src/services/Services.js @@ -9,14 +9,15 @@ class Services { this.config = { 'default': true, 'auth': 'qux', - 'api': "https://api-os.flowrabbit.ai", + 'api': "/api", 'user': { 'allowSignUp': true }, 'websocket': '', - 'proxy_URL': 'https://proxy-os.flowrabbit.ai', - 'api_URL': 'https://api-os.flowrabbit.ai', - 'node_URL': 'https://node-os.flowrabbit.ai' + 'proxy_URL': '', + 'api_URL': '/api', + 'app_URL': '/apps', + 'node_URL': '/docs' } } @@ -70,4 +71,4 @@ class Services { } } -export default new Services() \ No newline at end of file +export default new Services() diff --git a/apps/src/views/Home.vue b/apps/src/views/Home.vue index 8c94c2a..f963a2d 100644 --- a/apps/src/views/Home.vue +++ b/apps/src/views/Home.vue @@ -82,7 +82,7 @@ export default { }, domains: { }, - appURL: Services.getConfig().app_URL || "apps-os.flowrabbit.ai", + appURL: Services.getConfig().app_URL || "/apps", isAppStore: false } }, diff --git a/docker-compose.yaml b/docker-compose.yaml index e27cab9..b773220 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,18 +1,19 @@ x-common-env: &common-env + FLR_PUBLIC_URL: ${FLR_PUBLIC_URL:-http://localhost} TZ: America/Chicago FLR_AUTH_SERVICE: qux FLR_USER_ALLOW_SIGNUP: "true" FLR_USER_ALLOWED_DOMAINS: "*" FLR_INTERNAL_API_URL: http://backend:8080 FLR_INTERNAL_PROXY_URL: http://proxy:8084 - FLR_API_URL: https://api-os.flowrabbit.ai - FLR_APPS_URL: https://apps-os.flowrabbit.ai - FLR_PROXY_URL: https://proxy-os.flowrabbit.ai - FLR_NODE_URL: https://node-os.flowrabbit.ai - FLR_HTTP_HOST: https://studio-os.flowrabbit.ai - FLR_CLIENT_API_KEY: Put_Some_Secret_In_Here - FLR_ADMIN_EMAIL: email@example.com - FLR_ADMIN_PASSWORD: Put_Some_Secret_In_Here + FLR_API_URL: ${FLR_PUBLIC_URL:-http://localhost}/api + FLR_APPS_URL: ${FLR_PUBLIC_URL:-http://localhost}/apps + FLR_PROXY_URL: ${FLR_PUBLIC_URL:-http://localhost} + FLR_NODE_URL: ${FLR_PUBLIC_URL:-http://localhost}/docs + FLR_HTTP_HOST: ${FLR_PUBLIC_URL:-http://localhost} + FLR_CLIENT_API_KEY: ${FLR_CLIENT_API_KEY:?Set FLR_CLIENT_API_KEY} + FLR_ADMIN_EMAIL: ${FLR_ADMIN_EMAIL:?Set FLR_ADMIN_EMAIL} + FLR_ADMIN_PASSWORD: ${FLR_ADMIN_PASSWORD:?Set FLR_ADMIN_PASSWORD} services: traefik: @@ -20,48 +21,30 @@ services: command: # Providers - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" - "--api.dashboard=true" # Entrypoints - "--entrypoints.web.address=:80" - - "--entrypoints.websecure.address=:443" - - # Redirect all HTTP -> HTTPS - - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - - # ACME / Let's Encrypt - - "--certificatesresolvers.myresolver.acme.email=support@flowrabbit.ai" - - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" - - "--certificatesresolvers.myresolver.acme.httpchallenge=true" - - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web" - - "--certificatesresolvers.myresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory" - - # Request certs at startup (not just on first request) - - "--certificatesresolvers.myresolver.acme.tlschallenge=true" - - "--entrypoints.websecure.http.tls.certresolver=myresolver" - - "--entrypoints.websecure.http.tls.domains[0].main=docs-os.flowrabbit.ai" ports: - "80:80" - - "443:443" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - - "./letsencrypt:/letsencrypt" mongo: restart: always container_name: mongo image: mongo volumes: - - ./mongo-data:/data/db # pth for the data to be stored and kept on your host machine is on the left side of the ":" + - mongo-data:/data/db backend: restart: always container_name: backend image: flowrabbit/backend:latest volumes: - - ./app-data:/app-data + - app-data:/app-data environment: <<: *common-env FLR_HTTP_PORT: 8080 @@ -78,9 +61,12 @@ services: - mongo labels: - "traefik.enable=true" - - "traefik.http.routers.api.rule=Host(`api-os.flowrabbit.ai`)" # update here - - "traefik.http.routers.api.entrypoints=websecure" - - "traefik.http.routers.api.tls.certresolver=myresolver" + # Single-domain path-based routing + - "traefik.http.routers.api-path.rule=PathPrefix(`/api`)" + - "traefik.http.routers.api-path.entrypoints=web" + - "traefik.http.routers.api-path.priority=100" + - "traefik.http.routers.api-path.middlewares=api-strip" + - "traefik.http.middlewares.api-strip.stripprefix.prefixes=/api" - "traefik.http.services.api.loadbalancer.server.port=8080" studio: restart: always @@ -90,9 +76,10 @@ services: <<: *common-env labels: - "traefik.enable=true" - - "traefik.http.routers.studio.rule=Host(`studio-os.flowrabbit.ai`)" # update here - - "traefik.http.routers.studio.entrypoints=websecure" - - "traefik.http.routers.studio.tls.certresolver=myresolver" + # Single-domain path-based routing (default catch-all) + - "traefik.http.routers.studio-path.rule=PathPrefix(`/`)" + - "traefik.http.routers.studio-path.entrypoints=web" + - "traefik.http.routers.studio-path.priority=1" - "traefik.http.services.studio.loadbalancer.server.port=8082" proxy: restart: always @@ -102,9 +89,10 @@ services: <<: *common-env labels: - "traefik.enable=true" - - "traefik.http.routers.proxy.rule=Host(`proxy-os.flowrabbit.ai`)" #update here - - "traefik.http.routers.proxy.entrypoints=websecure" - - "traefik.http.routers.proxy.tls.certresolver=myresolver" + # Single-domain path-based routing + - "traefik.http.routers.proxy-path.rule=PathPrefix(`/proxy`) || PathPrefix(`/stream`) || PathPrefix(`/mcp`)" + - "traefik.http.routers.proxy-path.entrypoints=web" + - "traefik.http.routers.proxy-path.priority=100" - "traefik.http.services.proxy.loadbalancer.server.port=8084" apps: restart: always @@ -114,9 +102,12 @@ services: <<: *common-env labels: - "traefik.enable=true" - - "traefik.http.routers.apps.rule=Host(`apps-os.flowrabbit.ai`)" #update here - - "traefik.http.routers.apps.entrypoints=websecure" - - "traefik.http.routers.apps.tls.certresolver=myresolver" + # Single-domain path-based routing + - "traefik.http.routers.apps-path.rule=PathPrefix(`/apps`)" + - "traefik.http.routers.apps-path.entrypoints=web" + - "traefik.http.routers.apps-path.priority=100" + - "traefik.http.routers.apps-path.middlewares=apps-strip" + - "traefik.http.middlewares.apps-strip.stripprefix.prefixes=/apps" - "traefik.http.services.apps.loadbalancer.server.port=8083" docs: restart: always @@ -126,7 +117,14 @@ services: <<: *common-env labels: - "traefik.enable=true" - - "traefik.http.routers.docs.rule=Host(`docs-os.flowrabbit.ai`)" #update here - - "traefik.http.routers.docs.entrypoints=websecure" - - "traefik.http.routers.docs.tls.certresolver=myresolver" - - "traefik.http.services.docs.loadbalancer.server.port=8085" \ No newline at end of file + # Single-domain path-based routing + - "traefik.http.routers.docs-path.rule=PathPrefix(`/docs`)" + - "traefik.http.routers.docs-path.entrypoints=web" + - "traefik.http.routers.docs-path.priority=100" + - "traefik.http.routers.docs-path.middlewares=docs-strip" + - "traefik.http.middlewares.docs-strip.stripprefix.prefixes=/docs" + - "traefik.http.services.docs.loadbalancer.server.port=8085" + +volumes: + mongo-data: + app-data: From 8a243a312de37f0b01280921c5fbcfaf2b9f2a8c Mon Sep 17 00:00:00 2001 From: Siva Subramanian Date: Tue, 17 Feb 2026 22:59:17 +0100 Subject: [PATCH 2/2] chore(deploy): add env template and compose overrides for local and legacy modes --- .env.example | 14 +++++++++++ README.md | 40 ++++++++++++++++++++++++++++---- docker-compose.legacy-hosts.yaml | 30 ++++++++++++++++++++++++ docker-compose.local.yaml | 20 ++++++++++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.legacy-hosts.yaml create mode 100644 docker-compose.local.yaml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7606576 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Single-domain public URL (no trailing slash) +FLR_PUBLIC_URL=https://flowrabbit.example.com + +# Required secrets +FLR_CLIENT_API_KEY=replace-with-random-secret +FLR_ADMIN_EMAIL=admin@example.com +FLR_ADMIN_PASSWORD=replace-with-strong-password + +# Optional legacy hostnames (used only with docker-compose.legacy-hosts.yaml) +FLR_STUDIO_HOST=studio-os.flowrabbit.ai +FLR_API_HOST=api-os.flowrabbit.ai +FLR_PROXY_HOST=proxy-os.flowrabbit.ai +FLR_APPS_HOST=apps-os.flowrabbit.ai +FLR_DOCS_HOST=docs-os.flowrabbit.ai diff --git a/README.md b/README.md index 3bafa21..7034c72 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,44 @@ docker compose up to start Flowrabbit. Make sure you have installed Docker before and have a domain name. -Flowrabbit® Studio will spin up four servers, each needing their own subdomain. The easiest way to install to install -Flowrabbit® Studio is to use the following Docker Compose file. +Flowrabbit® Studio supports two deployment modes: +- Single-domain (recommended for Elestio / one-click hosting): path-based routing on one domain +- Legacy subdomain mode: host-based routing (`api.*`, `studio.*`, `proxy.*`, `apps.*`, `docs.*`) via override file -The docker compose file will also spin up a Traefik reverse proxy server to handle SSL. To make everything work, you need to -update the sub domain URLs in the beginning of the file (`FLR__HOST`). Do not forget to also update the `"traefik.http.routers..rule=Host(....)"` sections below. +Start by creating a `.env` file from `.env.example` and setting required secrets. + +For single-domain mode, set `FLR_PUBLIC_URL` in `docker-compose.yaml`, for example: + +> Note: The long compose snippet below is kept for reference and may lag behind current defaults. Use `docker-compose.yaml` in this repository as source of truth. + +```yaml +FLR_PUBLIC_URL: https://flowrabbit.example.com +``` + +In single-domain mode this will expose: + +- `https://flowrabbit.example.com/` -> studio +- `https://flowrabbit.example.com/api` -> backend +- `https://flowrabbit.example.com/proxy` -> proxy +- `https://flowrabbit.example.com/apps` -> apps +- `https://flowrabbit.example.com/docs` -> docs + +The docker compose file also spins up Traefik for routing on port `80`. +TLS should be terminated by your hosting platform (for example, Elestio ingress/load balancer). + +Use these commands: + +```sh +# Single-domain mode (default) +docker compose up -d + +# Local mode (forces public URL to localhost) +docker compose -f docker-compose.yaml -f docker-compose.local.yaml up -d + +# Legacy host-based mode (optional compatibility mode) +docker compose -f docker-compose.yaml -f docker-compose.legacy-hosts.yaml up -d +``` Also, you need to set the `FLR_CLIENT_API_KEY`, `FLR_JWT_PASSWORD`, `FLR_DB_ENCRYPTION_KEY` and `FLR_ADMIN_PASSWORD` diff --git a/docker-compose.legacy-hosts.yaml b/docker-compose.legacy-hosts.yaml new file mode 100644 index 0000000..8266b52 --- /dev/null +++ b/docker-compose.legacy-hosts.yaml @@ -0,0 +1,30 @@ +services: + backend: + labels: + - "traefik.http.routers.api.rule=Host(`${FLR_API_HOST:-api-os.flowrabbit.ai}`)" + - "traefik.http.routers.api.entrypoints=web" + - "traefik.http.routers.api.service=api" + + studio: + labels: + - "traefik.http.routers.studio.rule=Host(`${FLR_STUDIO_HOST:-studio-os.flowrabbit.ai}`)" + - "traefik.http.routers.studio.entrypoints=web" + - "traefik.http.routers.studio.service=studio" + + proxy: + labels: + - "traefik.http.routers.proxy.rule=Host(`${FLR_PROXY_HOST:-proxy-os.flowrabbit.ai}`)" + - "traefik.http.routers.proxy.entrypoints=web" + - "traefik.http.routers.proxy.service=proxy" + + apps: + labels: + - "traefik.http.routers.apps.rule=Host(`${FLR_APPS_HOST:-apps-os.flowrabbit.ai}`)" + - "traefik.http.routers.apps.entrypoints=web" + - "traefik.http.routers.apps.service=apps" + + docs: + labels: + - "traefik.http.routers.docs.rule=Host(`${FLR_DOCS_HOST:-docs-os.flowrabbit.ai}`)" + - "traefik.http.routers.docs.entrypoints=web" + - "traefik.http.routers.docs.service=docs" diff --git a/docker-compose.local.yaml b/docker-compose.local.yaml new file mode 100644 index 0000000..0f52f04 --- /dev/null +++ b/docker-compose.local.yaml @@ -0,0 +1,20 @@ +services: + backend: + environment: + FLR_PUBLIC_URL: http://localhost + + studio: + environment: + FLR_PUBLIC_URL: http://localhost + + proxy: + environment: + FLR_PUBLIC_URL: http://localhost + + apps: + environment: + FLR_PUBLIC_URL: http://localhost + + docs: + environment: + FLR_PUBLIC_URL: http://localhost