Create a Docker-based development workflow with hot-reload for both frontend (Vite/Svelte) and backend (uvicorn/Python), eliminating the need to rebuild the image on every code change.
Goals
Hot-reload for frontend changes (Vite HMR)
Hot-reload for backend changes (uvicorn --reload)
Share data volume with production compose
Support OrbStack HTTPS access
Files Created
Dockerfile.dev - Development image with Node.js 22 + Python 3.11
docker-compose.dev.yaml - Dev compose with volume mounts
scripts/dev-entrypoint.sh - Entrypoint running both servers
Usage
docker compose -f docker-compose.dev.yaml up --build # First time
docker compose -f docker-compose.dev.yaml up # Subsequent runs
Access via https://open-webui.open-webui.orb.local or http://localhost:5173
Originally created by @vlthr on GitHub (Jan 7, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/20444
## Summary
Create a Docker-based development workflow with hot-reload for both frontend (Vite/Svelte) and backend (uvicorn/Python), eliminating the need to rebuild the image on every code change.
## Goals
- Hot-reload for frontend changes (Vite HMR)
- Hot-reload for backend changes (uvicorn --reload)
- Share data volume with production compose
- Support OrbStack HTTPS access
## Files Created
- `Dockerfile.dev` - Development image with Node.js 22 + Python 3.11
- `docker-compose.dev.yaml` - Dev compose with volume mounts
- `scripts/dev-entrypoint.sh` - Entrypoint running both servers
## Usage
```bash
docker compose -f docker-compose.dev.yaml up --build # First time
docker compose -f docker-compose.dev.yaml up # Subsequent runs
```
Access via `https://open-webui.open-webui.orb.local` or `http://localhost:5173`
Explored native dev setup (npm run dev + backend/dev.sh)
Found src/lib/constants.ts auto-detects dev mode and points to port 8080
No Vite proxy needed by default - frontend knows to talk to backend directly
Process Management
Initial approach using sh -c "cmd1 & cmd2" has problems (no signal propagation, no crash detection)
Solution: Use --init flag (adds tini as PID 1) + bash wrapper with wait -n
File Watching on macOS Docker
OrbStack: Native inotify support works - polling NOT needed
Docker Desktop: Unreliable inotify - requires WATCHFILES_FORCE_POLLING=true and CHOKIDAR_USEPOLLING=true
Volume Strategy
Mount entire project directory .:/app:cached for simplicity
Anonymous volume - /app/node_modules preserves container's Linux binaries
Named volume open-webui:/app/backend/data shares data with production compose
<!-- gh-comment-id:3718549872 -->
@vlthr commented on GitHub (Jan 7, 2026):
## Progress Notes
### Research Phase
- Explored native dev setup (`npm run dev` + `backend/dev.sh`)
- Found `src/lib/constants.ts` auto-detects dev mode and points to port 8080
- No Vite proxy needed by default - frontend knows to talk to backend directly
### Process Management
- Initial approach using `sh -c "cmd1 & cmd2"` has problems (no signal propagation, no crash detection)
- Solution: Use `--init` flag (adds tini as PID 1) + bash wrapper with `wait -n`
### File Watching on macOS Docker
- **OrbStack**: Native inotify support works - polling NOT needed
- **Docker Desktop**: Unreliable inotify - requires `WATCHFILES_FORCE_POLLING=true` and `CHOKIDAR_USEPOLLING=true`
### Volume Strategy
- Mount entire project directory `.:/app:cached` for simplicity
- Anonymous volume `- /app/node_modules` preserves container's Linux binaries
- Named volume `open-webui:/app/backend/data` shares data with production compose
Accessing via OrbStack's HTTPS URL (https://open-webui.open-webui.orb.local) causes mixed content errors - the frontend tries to make HTTP requests to port 8080.
Options Considered
CORS only - Works for http://localhost:5173 but not HTTPS (mixed content)
Vite proxy - Routes API through frontend, same-origin requests, no mixed content
OrbStack multi-port HTTPS - Investigated but not supported (see below)
OrbStack Limitation
Researched whether OrbStack container labels could terminate HTTPS on multiple ports. Not supported.
dev.orbstack.http-port only allows ONE port for HTTPS termination
Added optional proxy support to vite.config.ts via VITE_API_PROXY_TARGET env var:
When set, Vite proxies /api, /ollama, /openai, /socket.io to backend
PUBLIC_API_BASE_URL= (empty) makes frontend use same-origin requests
No changes to default behavior - proxy only activates when env var is set
Vite Bug
__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS env var doesn't work unless allowedHosts array already exists in config. Had to hardcode .open-webui.orb.local in vite.config.ts for now.
<!-- gh-comment-id:3718550693 -->
@vlthr commented on GitHub (Jan 7, 2026):
## HTTPS Access via OrbStack
### Problem
Accessing via OrbStack's HTTPS URL (`https://open-webui.open-webui.orb.local`) causes mixed content errors - the frontend tries to make HTTP requests to port 8080.
### Options Considered
1. **CORS only** - Works for `http://localhost:5173` but not HTTPS (mixed content)
2. **Vite proxy** - Routes API through frontend, same-origin requests, no mixed content
3. **OrbStack multi-port HTTPS** - Investigated but not supported (see below)
### OrbStack Limitation
Researched whether OrbStack container labels could terminate HTTPS on multiple ports. **Not supported.**
- `dev.orbstack.http-port` only allows ONE port for HTTPS termination
- Feature request: [orbstack/orbstack#1165](https://github.com/orbstack/orbstack/issues/1165) (29+ upvotes, still open)
- No Traefik-style `domain@port` syntax available
### Solution: Vite Proxy (env var controlled)
Added optional proxy support to `vite.config.ts` via `VITE_API_PROXY_TARGET` env var:
- When set, Vite proxies `/api`, `/ollama`, `/openai`, `/socket.io` to backend
- `PUBLIC_API_BASE_URL=` (empty) makes frontend use same-origin requests
- No changes to default behavior - proxy only activates when env var is set
### Vite Bug
`__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` env var doesn't work unless `allowedHosts` array already exists in config. Had to hardcode `.open-webui.orb.local` in vite.config.ts for now.
Added optional VITE_API_PROXY_TARGET env var support for API proxying
Added allowedHosts: ['.open-webui.orb.local'] for OrbStack access
src/lib/constants.ts
Added PUBLIC_API_BASE_URL env var override for API base URL
Default behavior unchanged - only activates when env var is set
These changes add flexibility without affecting default behavior and could be upstreamed.
Local-only Files
Dockerfile.dev
docker-compose.dev.yaml
scripts/dev-entrypoint.sh
<!-- gh-comment-id:3718551186 -->
@vlthr commented on GitHub (Jan 7, 2026):
## Changes to Upstream Files
### `vite.config.ts`
- Added optional `VITE_API_PROXY_TARGET` env var support for API proxying
- Added `allowedHosts: ['.open-webui.orb.local']` for OrbStack access
### `src/lib/constants.ts`
- Added `PUBLIC_API_BASE_URL` env var override for API base URL
- Default behavior unchanged - only activates when env var is set
These changes add flexibility without affecting default behavior and could be upstreamed.
## Local-only Files
- `Dockerfile.dev`
- `docker-compose.dev.yaml`
- `scripts/dev-entrypoint.sh`
Development setup complete and working. Access via https://open-webui.open-webui.orb.local or http://localhost:5173.
<!-- gh-comment-id:3718551456 -->
@vlthr commented on GitHub (Jan 7, 2026):
Development setup complete and working. Access via `https://open-webui.open-webui.orb.local` or `http://localhost:5173`.
ENABLE_BASE_MODELS_CACHE=false - skip model pre-caching
OrbStack vs Docker Desktop
OrbStack: Native inotify works, no polling needed
Docker Desktop: May need WATCHFILES_FORCE_POLLING=true + CHOKIDAR_USEPOLLING=true
Volume Strategy
Mount .:/app:cached for hot reload
Anonymous volume /app/node_modules preserves Linux binaries
Named volume open-webui:/app/backend/data shares with production
<!-- gh-comment-id:3718712110 -->
@vlthr commented on GitHub (Jan 7, 2026):
## Final Summary - Decisions & Fixes
### Files Created
- `Dockerfile.dev` - Node 22 + Python 3.11 dev image
- `docker-compose.dev.yaml` - Dev compose with volume mounts
- `scripts/dev-entrypoint.sh` - Runs both servers with proper signal handling
### Key Fixes
**Process Management**
- Use `--init` flag (tini) for signal propagation
- `wait -n` in bash for crash detection
- Backend starts first, wait for port 8080, then frontend
**Static Files**
- `config.py` deletes STATIC_DIR contents at import then copies from `FRONTEND_BUILD_DIR/static`
- Fix: Set `FRONTEND_BUILD_DIR=/app` so it copies from `/app/static`
**HTTPS via OrbStack**
- Vite proxy routes `/api`, `/ollama`, `/openai`, `/ws/socket.io` to backend
- `PUBLIC_API_BASE_URL=` (empty) makes frontend use same-origin
- `allowedHosts: ['.open-webui.orb.local']` in vite.config.ts
- `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` env var is buggy - doesn't work without existing array
**WebSocket**
- Backend uses `/ws/socket.io` path (not `/socket.io`)
- Vite proxy must match `/ws/socket.io`
**LiveKit**
- `LIVEKIT_URL=ws://livekit:7880` - internal Docker URL for backend
- `LIVEKIT_PUBLIC_URL=wss://livekit.open-webui.orb.local` - browser uses OrbStack HTTPS
**Cold Start Optimization**
- `BYPASS_EMBEDDING_AND_RETRIEVAL=true` - skip SentenceTransformer loading
- `ENABLE_BASE_MODELS_CACHE=false` - skip model pre-caching
**OrbStack vs Docker Desktop**
- OrbStack: Native inotify works, no polling needed
- Docker Desktop: May need `WATCHFILES_FORCE_POLLING=true` + `CHOKIDAR_USEPOLLING=true`
**Volume Strategy**
- Mount `.:/app:cached` for hot reload
- Anonymous volume `/app/node_modules` preserves Linux binaries
- Named volume `open-webui:/app/backend/data` shares with production
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @vlthr on GitHub (Jan 7, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/20444
Summary
Create a Docker-based development workflow with hot-reload for both frontend (Vite/Svelte) and backend (uvicorn/Python), eliminating the need to rebuild the image on every code change.
Goals
Files Created
Dockerfile.dev- Development image with Node.js 22 + Python 3.11docker-compose.dev.yaml- Dev compose with volume mountsscripts/dev-entrypoint.sh- Entrypoint running both serversUsage
Access via
https://open-webui.open-webui.orb.localorhttp://localhost:5173@vlthr commented on GitHub (Jan 7, 2026):
Progress Notes
Research Phase
npm run dev+backend/dev.sh)src/lib/constants.tsauto-detects dev mode and points to port 8080Process Management
sh -c "cmd1 & cmd2"has problems (no signal propagation, no crash detection)--initflag (adds tini as PID 1) + bash wrapper withwait -nFile Watching on macOS Docker
WATCHFILES_FORCE_POLLING=trueandCHOKIDAR_USEPOLLING=trueVolume Strategy
.:/app:cachedfor simplicity- /app/node_modulespreserves container's Linux binariesopen-webui:/app/backend/datashares data with production compose@vlthr commented on GitHub (Jan 7, 2026):
HTTPS Access via OrbStack
Problem
Accessing via OrbStack's HTTPS URL (
https://open-webui.open-webui.orb.local) causes mixed content errors - the frontend tries to make HTTP requests to port 8080.Options Considered
http://localhost:5173but not HTTPS (mixed content)OrbStack Limitation
Researched whether OrbStack container labels could terminate HTTPS on multiple ports. Not supported.
dev.orbstack.http-portonly allows ONE port for HTTPS terminationdomain@portsyntax availableSolution: Vite Proxy (env var controlled)
Added optional proxy support to
vite.config.tsviaVITE_API_PROXY_TARGETenv var:/api,/ollama,/openai,/socket.ioto backendPUBLIC_API_BASE_URL=(empty) makes frontend use same-origin requestsVite Bug
__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTSenv var doesn't work unlessallowedHostsarray already exists in config. Had to hardcode.open-webui.orb.localin vite.config.ts for now.@vlthr commented on GitHub (Jan 7, 2026):
Changes to Upstream Files
vite.config.tsVITE_API_PROXY_TARGETenv var support for API proxyingallowedHosts: ['.open-webui.orb.local']for OrbStack accesssrc/lib/constants.tsPUBLIC_API_BASE_URLenv var override for API base URLThese changes add flexibility without affecting default behavior and could be upstreamed.
Local-only Files
Dockerfile.devdocker-compose.dev.yamlscripts/dev-entrypoint.sh@vlthr commented on GitHub (Jan 7, 2026):
Development setup complete and working. Access via
https://open-webui.open-webui.orb.localorhttp://localhost:5173.@vlthr commented on GitHub (Jan 7, 2026):
Final Summary - Decisions & Fixes
Files Created
Dockerfile.dev- Node 22 + Python 3.11 dev imagedocker-compose.dev.yaml- Dev compose with volume mountsscripts/dev-entrypoint.sh- Runs both servers with proper signal handlingKey Fixes
Process Management
--initflag (tini) for signal propagationwait -nin bash for crash detectionStatic Files
config.pydeletes STATIC_DIR contents at import then copies fromFRONTEND_BUILD_DIR/staticFRONTEND_BUILD_DIR=/appso it copies from/app/staticHTTPS via OrbStack
/api,/ollama,/openai,/ws/socket.ioto backendPUBLIC_API_BASE_URL=(empty) makes frontend use same-originallowedHosts: ['.open-webui.orb.local']in vite.config.ts__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTSenv var is buggy - doesn't work without existing arrayWebSocket
/ws/socket.iopath (not/socket.io)/ws/socket.ioLiveKit
LIVEKIT_URL=ws://livekit:7880- internal Docker URL for backendLIVEKIT_PUBLIC_URL=wss://livekit.open-webui.orb.local- browser uses OrbStack HTTPSCold Start Optimization
BYPASS_EMBEDDING_AND_RETRIEVAL=true- skip SentenceTransformer loadingENABLE_BASE_MODELS_CACHE=false- skip model pre-cachingOrbStack vs Docker Desktop
WATCHFILES_FORCE_POLLING=true+CHOKIDAR_USEPOLLING=trueVolume Strategy
.:/app:cachedfor hot reload/app/node_modulespreserves Linux binariesopen-webui:/app/backend/datashares with production@vlthr commented on GitHub (Jan 7, 2026):
Updated with final summary of all decisions and fixes.