Docker
One container for the full Web app. Images on GitHub Container Registry:
ghcr.io/hkuds/deeptutor:latest— stable releaseghcr.io/hkuds/deeptutor:pre— pre-release, when available
docker run --rm --name deeptutor \ -p 127.0.0.1:3782:3782 \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latestOnly
3782needs to be published. The browser talks exclusively to the frontend origin; the Next.js middleware (web/proxy.ts) forwards every/api/*and/ws/*request to the FastAPI backend inside the container, so you don’t expose8001to the host for the UI to work. Publishing it (-p 127.0.0.1:8001:8001) is optional — handy only for hitting the API directly with curl or scripts.
Open http://127.0.0.1:3782. The container creates /app/data/user/settings/*.json on first boot; configure model providers from the Web Settings page. Config, API keys, logs, workspace files, memory, knowledge bases, and Partner workspaces (data/partners/<id>/) all persist in the deeptutor-data volume.
Detached mode
Section titled “Detached mode”Add -d to run in the background:
docker run -d --name deeptutor \ -p 127.0.0.1:3782:3782 \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latest
docker logs -f deeptutor # follow logsdocker stop deeptutor # stopdocker rm deeptutor # remove (volume persists)The deeptutor-data volume keeps your settings and workspace across stop / rm cycles. To truly start over: docker volume rm deeptutor-data.
Remote Docker / reverse proxy
Section titled “Remote Docker / reverse proxy”The browser only ever talks to the frontend origin (:3782); the in-container
Next.js middleware forwards /api/* and /ws/* to the backend on
localhost:8001 server-side. So for the common single-container case you do
not need to configure an API base at all — just point your reverse proxy /
TLS terminator at the published :3782:
deeptutor.example.com { reverse_proxy localhost:3782}You only need an API base for a split deployment, where the backend runs in a
separate container or host. Set it in data/user/settings/system.json in the
mounted volume to the in-network address the frontend server uses to reach
the backend — it’s read server-side by the proxy, not sent to the browser:
{ "next_public_api_base": "http://backend:8001"}next_public_api_base_external (and its alias public_api_base) are accepted as
lower-precedence fallbacks and normalized on save.
CORS only matters with auth enabled and a cross-origin frontend: cors_origins
lists the frontend page origins allowed to call the backend. With auth disabled,
DeepTutor permits normal HTTP/HTTPS browser origins by default.
{ "cors_origins": ["https://deeptutor.example.com"]}Different host ports
Section titled “Different host ports”Change the left side of the -p host:container mapping:
docker run --rm --name deeptutor \ -p 127.0.0.1:8088:3782 \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latestAdd -p 127.0.0.1:8089:8001 as well if you also want the API exposed on the host. If you change container-side ports in /app/data/user/settings/system.json, restart and update the right side of each mapping to match.
Connecting to a local LLM (Ollama / LM Studio / llama.cpp / vLLM)
Section titled “Connecting to a local LLM (Ollama / LM Studio / llama.cpp / vLLM)”Inside Docker, localhost is the container itself, not your host machine. To reach a model service running on the host, use the host gateway:
docker run --rm --name deeptutor \ -p 127.0.0.1:3782:3782 \ --add-host=host.docker.internal:host-gateway \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latestThen in Settings → LLM (or Embedding), point the provider Base URL at host.docker.internal:
| Service | Base URL |
|---|---|
| Ollama LLM | http://host.docker.internal:11434/v1 |
| Ollama embedding | http://host.docker.internal:11434/api/embed |
| LM Studio | http://host.docker.internal:1234/v1 |
| llama.cpp | http://host.docker.internal:8080/v1 |
Docker Desktop (macOS / Windows) usually resolves host.docker.internal without --add-host. On Linux, the flag is the portable way to create that hostname on modern Docker Engine.
Linux alternative — host networking
Section titled “Linux alternative — host networking”Add --network=host and drop the -p flags:
docker run --rm --name deeptutor \ --network=host \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latestThe container shares the host network directly, so open http://127.0.0.1:3782 (or the frontend_port in system.json), and host services can be reached with normal localhost URLs like http://127.0.0.1:11434/v1.
Host networking exposes container ports directly on the host and may conflict with existing services.
Updating
Section titled “Updating”docker pull ghcr.io/hkuds/deeptutor:latestdocker rm -f deeptutor 2>/dev/nulldocker run --rm --name deeptutor \ -p 127.0.0.1:3782:3782 \ -v deeptutor-data:/app/data \ ghcr.io/hkuds/deeptutor:latestThe volume persists — your settings, KBs, memory all survive the upgrade.
Common errors
Section titled “Common errors”Bind for 0.0.0.0:3782 failed: port is already allocated
Section titled “Bind for 0.0.0.0:3782 failed: port is already allocated”lsof -i :3782 # macOSss -ltnp | grep :3782 # LinuxKill the conflicting process, or choose different host ports in your -p mapping.
Container exits immediately
Section titled “Container exits immediately”docker logs deeptutor | tail -30Most common: bad LLM API key on first boot (the backend fails fast). Open Settings → LLM in the Web UI before configuring an LLM, or remove the bad credential from the volume:
docker run --rm -v deeptutor-data:/app/data alpine \ sh -c 'rm /app/data/user/settings/model_catalog.json'docker run ... ghcr.io/hkuds/deeptutor:latest # restart with clean catalogFrontend loads but API calls fail (split deployment)
Section titled “Frontend loads but API calls fail (split deployment)”With the single-container image this shouldn’t happen — the in-container proxy
forwards /api/* to the backend automatically. If you run a split
deployment with the backend in a separate container or host, set
next_public_api_base in data/user/settings/system.json (inside the volume) to
the in-network address the frontend server uses to reach the backend, then
restart.
Multi-user mode in Docker
Section titled “Multi-user mode in Docker”Just toggle auth in the volume’s data/user/settings/auth.json ("enabled": true) and restart. The container picks it up. See Multi-User Deployment for the full setup.
More: Troubleshooting.
- Multi-User Deployment — team setup
- Explore DeepTutor — tour the running app