Skip to content

Docker

One container for the full Web app. Images on GitHub Container Registry:

  • ghcr.io/hkuds/deeptutor:latest — stable release
  • ghcr.io/hkuds/deeptutor:pre — pre-release, when available
Terminal window
docker run --rm --name deeptutor \
-p 127.0.0.1:3782:3782 \
-v deeptutor-data:/app/data \
ghcr.io/hkuds/deeptutor:latest

Only 3782 needs 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 expose 8001 to 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.

Add -d to run in the background:

Terminal window
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 logs
docker stop deeptutor # stop
docker 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.

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"]
}

Change the left side of the -p host:container mapping:

Terminal window
docker run --rm --name deeptutor \
-p 127.0.0.1:8088:3782 \
-v deeptutor-data:/app/data \
ghcr.io/hkuds/deeptutor:latest

Add -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:

Terminal window
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:latest

Then in Settings → LLM (or Embedding), point the provider Base URL at host.docker.internal:

ServiceBase URL
Ollama LLMhttp://host.docker.internal:11434/v1
Ollama embeddinghttp://host.docker.internal:11434/api/embed
LM Studiohttp://host.docker.internal:1234/v1
llama.cpphttp://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.

Add --network=host and drop the -p flags:

Terminal window
docker run --rm --name deeptutor \
--network=host \
-v deeptutor-data:/app/data \
ghcr.io/hkuds/deeptutor:latest

The 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.

Terminal window
docker pull ghcr.io/hkuds/deeptutor:latest
docker rm -f deeptutor 2>/dev/null
docker run --rm --name deeptutor \
-p 127.0.0.1:3782:3782 \
-v deeptutor-data:/app/data \
ghcr.io/hkuds/deeptutor:latest

The volume persists — your settings, KBs, memory all survive the upgrade.

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”
Terminal window
lsof -i :3782 # macOS
ss -ltnp | grep :3782 # Linux

Kill the conflicting process, or choose different host ports in your -p mapping.

Terminal window
docker logs deeptutor | tail -30

Most 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:

Terminal window
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 catalog

Frontend 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.

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.