Dify with Docker and Ollama: Build Your Own RAG Chatbot (Linux, Windows, Mac)
Before you start
- 17 min read
- 22 to run in terminal
- 5 file contents
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Dify is an open-source platform that turns your documents and a local Ollama model into a chatbot you can open in the browser, with source citations under every answer.
Dify handles the boring parts: it cuts your documents into chunks, stores them, finds the right chunks for each question and builds the prompt. Ollama runs the models on your own machine, so the chat model and the embedding model never leave your computer. That makes it a good fit for a private manual bot, an internal FAQ or a study helper. Dify is a big stack (about 16 containers), so this guide sticks to one clean path.
| What | Value |
|---|---|
| Dify version | 1.18.0 (released 2026-10-23, read on 2026-10-26) |
| Time needed | About 30 minutes, plus the model downloads |
| Size and RAM | Not measured by us. Dify's own minimum is 2 CPU cores and 4 GiB RAM, and the models come on top |
| Ports | 8080 for the web app (we change the default 80); 5003 plugin debugging is published by default, we lock it down |
| Official docs | docs.dify.ai |
A note on the version. We checked GitHub issues for 1.18.0. Users reported two specific bugs with external tool servers (issue 44635, closed on 24 October with a fix that is not yet in a tagged release as far as we could confirm, and issue 44742, still open), and nothing about failed installs or lost data. This guide does not use them, so it uses the latest release. The release is only a few days old, though. If you want the most boring choice, pin the previous release, 1.17.1, instead: the steps below stay the same, only the tag changes.
How it fits together#
You ask in the browser, Dify finds matching chunks in your knowledge base, and the plugin daemon sends the question plus those chunks to Ollama on your computer.
Ollama stays outside Docker. That is on purpose: on a Mac, Docker cannot give a container the GPU, and on Windows and Linux a native Ollama is the simplest way to use your graphics card.
Before you start#
You need Docker with Compose 2.24.0 or newer, git, and Ollama installed natively on the same computer.
| System | What to have |
|---|---|
| Linux | Docker Engine 19.03 or newer, Compose 2.24.0 or newer, git, openssl |
| Windows | Windows 10 22H2 or newer, Docker Desktop with the WSL2 backend, an Ubuntu WSL distribution. You run the Dify commands inside WSL, not in PowerShell |
| Mac | macOS Sonoma 14 or newer (needed by current Ollama), Docker Desktop with at least 2 CPUs and 8 GiB RAM given to it, git |
Ollama needs room for the models too. The chat model used here (gemma4:e2b) is a download of about 7 GB and Ollama suggests 8 GB of free VRAM or Mac memory for it. Smaller machines can pick a smaller model from the Ollama library.
Check the tools:
docker --version docker compose version git --version
- [ ] Docker Compose is 2.24.0 or newer
- [ ] Port 8080 is free (or pick another one)
- [ ] You have a backup of anything important on this machine
- [ ] For the Traefik section only: a domain, DNS and ports 80 and 443 open
Not run by us on all three systems: the steps follow the official Dify 1.18.0 docs and the real 1.18.0 Compose file, checked on 2026-10-26.
Step 1: Get Dify 1.18.0 and keep its data in one place#
Clone the exact release into ~/docker_volumes/dify, so everything Dify stores sits under one folder.
Dify's Compose file keeps its data in docker/volumes next to the Compose file. So we put the whole checkout under ~/docker_volumes/dify/source, and the data ends up in ~/docker_volumes/dify/source/docker/volumes. Backups go to ~/docker_volumes/dify/backups. The git tag is 1.18.0, without a v.
Linux#
mkdir -p ~/docker_volumes/dify/backups git clone --branch 1.18.0 --single-branch https://github.com/langgenius/dify.git ~/docker_volumes/dify/source cd ~/docker_volumes/dify/source git describe --tags --exact-match cd docker
Windows (PowerShell)#
Open your Ubuntu shell from PowerShell, then run the Linux commands inside it. Keep the files in the WSL home folder, not under /mnt/c, because file access there is slow.
wsl
mkdir -p ~/docker_volumes/dify/backups git clone --branch 1.18.0 --single-branch https://github.com/langgenius/dify.git ~/docker_volumes/dify/source cd ~/docker_volumes/dify/source git describe --tags --exact-match cd docker
Mac (Terminal)#
Same commands as Linux:
mkdir -p ~/docker_volumes/dify/backups git clone --branch 1.18.0 --single-branch https://github.com/langgenius/dify.git ~/docker_volumes/dify/source cd ~/docker_volumes/dify/source git describe --tags --exact-match cd docker
On a Mac, Docker Desktop must be allowed to share your home folder. It is by default.
Check it worked: git describe prints 1.18.0.
Step 2: Create the settings file and the secrets#
Copy .env.example to .env, set three secrets and move the web port off port 80.
cp .env.example .env chmod 600 .env openssl rand -base64 42 openssl rand -base64 32 openssl rand -base64 32
Open .env in an editor and set these. Use one generated value for each line:
SECRET_KEY=<first value> DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=<second value> DIFY_AGENT_API_TOKEN=<third value> EXPOSE_NGINX_PORT=127.0.0.1:8080 EXPOSE_NGINX_SSL_PORT=127.0.0.1:8443 EXPOSE_PLUGIN_DEBUGGING_PORT=127.0.0.1:5003
What these do:
SECRET_KEYsigns your sessions. Keep it forever: changing it later logs everyone out and can make saved credentials unreadable.DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKENis required by the 1.18.0 agent sandbox. If it stays empty, that container will not start.DIFY_AGENT_API_TOKENreplaces a development default that ships in the file.- The three
EXPOSE_...lines put the web app and the plugin debug port on127.0.0.1only. Compose builds the port as127.0.0.1:8080:80, so nobody else on your network can reach them. Other computers can use Dify later through Traefik (below) or if you remove127.0.0.1:.
Never commit .env or paste it in a chat. Also change DB_PASSWORD and REDIS_PASSWORD now if you like; they are only used inside the Docker network, but a change after the first start will not reach an existing database.
Check it worked: docker compose config --quiet prints nothing and exits without an error. Do not run plain docker compose config and share the output, it contains your secrets.
Dify 1.18.0 splits optional settings into docker/envs/. You do not need them for this guide.
Linux#
Run the commands above in the docker folder.
Windows (PowerShell)#
Run them in the WSL shell from Step 1 (openssl is included in Ubuntu). Edit .env with nano .env there.
Mac (Terminal)#
Same commands as Linux. openssl is already on macOS.
Step 3: Start Dify#
Pull the images and start the stack, then wait until everything is up.
Linux#
docker compose up -d docker compose ps -a
Windows (PowerShell)#
In the WSL shell from Step 1 (Docker Desktop must be running):
docker compose up -d docker compose ps -a
Mac (Terminal)#
Same commands as Linux:
docker compose up -d docker compose ps -a
The first start downloads a lot of images, so give it a few minutes. With the default settings Compose starts about 16 containers: api, api_websocket, worker, worker_beat, web, plugin_daemon, agent_backend, sandbox, local_sandbox, ssrf_proxy, agent_ssrf_proxy, nginx, redis, db_postgres, weaviate and a one-time init_permissions. That last one shows as exited when it has done its job. That is normal.
Check it worked: all long-running containers show Up (or healthy). If one keeps restarting, see Troubleshooting.
Step 4: Create the admin account#
Open the install page once and create the first administrator.
Open http://localhost:8080/install in your browser, fill in email, name and password, then sign in at http://localhost:8080. Do this right away, while only your own machine can reach Dify.
Check it worked: you see the Dify Studio page after signing in.
Linux#
The page is the same on every system: http://localhost:8080/install.
Windows (PowerShell)#
Same page. Open it in your Windows browser.
Mac (Terminal)#
Same page.
Step 5: Let Docker reach Ollama#
Install two models in Ollama and make it reachable from the containers.
First pull the chat model and the embedding model. The chat model writes answers. The embedding model turns text into numbers so Dify can search by meaning.
Linux#
ollama pull gemma4:e2b ollama pull embeddinggemma ollama list
By default Ollama listens on 127.0.0.1:11434 only. A container cannot reach that, so make Ollama listen on all interfaces:
sudo systemctl edit ollama.service
Add these lines in the editor, save, then restart:
[Service] Environment="OLLAMA_HOST=0.0.0.0:11434"
sudo systemctl daemon-reload sudo systemctl restart ollama
Now Ollama listens on every network interface of the computer. Block port 11434 from outside in your firewall and never forward it on your router.
Then teach the plugin container the name host.docker.internal. Create docker-compose.override.yaml next to docker-compose.yaml (Compose reads it automatically):
services:
plugin_daemon:
extra_hosts:
- "host.docker.internal:host-gateway"
docker compose up -d
Windows (PowerShell)#
Install Ollama for Windows, then in PowerShell:
ollama pull gemma4:e2b ollama pull embeddinggemma ollama list
Docker Desktop already provides the name host.docker.internal, so try the next step without changing anything. If Dify later says the connection is refused, make Ollama listen on all interfaces: quit Ollama from the tray, open "Edit environment variables for your account", add OLLAMA_HOST with the value 0.0.0.0:11434, then start Ollama again from the Start menu. Keep Windows Firewall closed to the outside for port 11434.
Mac (Terminal)#
Install the Ollama app, then:
ollama pull gemma4:e2b ollama pull embeddinggemma ollama list
Docker Desktop provides host.docker.internal here too, so try the next step first. If the connection is refused:
launchctl setenv OLLAMA_HOST "0.0.0.0:11434"
Then quit and start the Ollama app again.
Check it worked: test the embedding model from the host.
curl http://localhost:11434/api/embed -H 'Content-Type: application/json' -d '{"model":"embeddinggemma","input":"A document retrieval test."}'
You should get back a JSON object with an embeddings list of numbers. We did not run this here, so the exact numbers are not shown.
Step 6: Add the Ollama models to Dify#
Install the Ollama plugin from the Dify Marketplace and register one chat model and one embedding model.
The exact menu names change between Dify releases, so use these as a guide:
- In Dify, open the plugins area and search the Marketplace for the Ollama model provider. Install it. (This needs internet access once.)
- Open the Model Provider settings and add a model from Ollama.
- Chat model: name
gemma4:e2b, type LLM, base URLhttp://host.docker.internal:11434. - Embedding model: name
embeddinggemma, type Text Embedding, the same base URL. - Save both. Dify checks the connection when you save.
Use the model name exactly as ollama list shows it. Do not use http://localhost:11434 here: inside a container, localhost is the container itself, not your computer.
Check it worked: both models save without an error and appear in the model list.
Linux#
This step happens in the browser, so it is the same on every system.
Windows (PowerShell)#
Same, in your Windows browser.
Mac (Terminal)#
Same, in your Mac browser.
Step 7: Build the knowledge base and the chatbot#
Upload a few documents, index them with the embedding model, then attach them to a Chatbot app.
- Open Knowledge, create a knowledge base and upload a few real documents. Start small: one manual, one FAQ and one file with exact codes or part numbers. That gives you both meaning questions and exact-match questions to test with.
- Chunking: choose General mode. A reasonable first try is a
\n\ndelimiter, 1,000 characters per chunk and 100 characters of overlap. These are our starting values, not official ones, so look at the preview and adjust. Lengths count characters, not tokens. If procedures or tables lose their context, try Parent-child mode, which matches small pieces but hands the model the larger parent text. The chunk mode cannot be changed after you create the knowledge base. - Indexing: choose High Quality and pick
embeddinggemma. High Quality cannot be switched back to Economical later. Use the same embedding model for indexing and searching. - Retrieval: for technical documents, try hybrid search (meaning plus exact words). Leave Top K and the score threshold at their defaults first, then tune them with Retrieval Test.
- Open Studio, create an app, choose Chatbot, and under Context add your knowledge base. Select
gemma4:e2bas the model. Turn on Citation and Attribution. - A simple system prompt:
Answer using the supplied knowledge context. If the documents do not contain enough information, say that the available documents do not answer the question. Keep units, part numbers and version numbers exactly as written. Do not invent document references.
- Test in Debug and Preview, then publish.
Test at least four questions: one answered directly in a document, one worded differently, one with an exact code, and one the documents cannot answer. The last one should get an honest "not in the documents".
Check it worked: answers show a citation block with the source file under them.
Linux#
This step happens in the browser, so it is the same on every system.
Windows (PowerShell)#
Same, in your Windows browser.
Mac (Terminal)#
Same, in your Mac browser.
Put it behind HTTPS with Traefik#
On a server with a domain, let Traefik serve HTTPS in front of Dify's own nginx. Local-only users can skip this section.
This assumes Traefik v3 already runs with the Docker provider, sits on a Docker network called proxy, has an entrypoint called https and can get a certificate for your host name. Add this to docker-compose.override.yaml (keep the plugin_daemon part on Linux):
services:
nginx:
ports: !reset []
networks:
- default
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.dify.rule=Host(`dify.<your-domain>`)"
- "traefik.http.routers.dify.entrypoints=https"
- "traefik.http.routers.dify.tls=true"
- "traefik.http.services.dify.loadbalancer.server.port=80"
networks:
proxy:
external: true
!reset [] removes the published port, so only Traefik reaches nginx. Create the network once if it is missing (docker network create proxy). If you use a certificate resolver, add traefik.http.routers.dify.tls.certresolver=<your-resolver>.
Then tell Dify its public address in .env, and restart:
TRIGGER_URL=https://dify.<your-domain>
ENDPOINT_URL_TEMPLATE=https://dify.<your-domain>/e/{hook_id}
NEXT_PUBLIC_SOCKET_URL=wss://dify.<your-domain>
FILES_URL=https://dify.<your-domain>
docker compose up -d
If you still see localhost in links, look through .env for other URL settings. If Traefik or a load balancer sits in front of Dify's nginx, read the proxy and client IP part of the 1.18.0 release notes. HTTPS only encrypts the traffic. It does not log anyone in, so put an authentication layer or a private network in front of Dify.
Check it worked: curl -I https://dify.<your-domain>/ answers, and port 8080 is no longer open from outside.
Security and testing#
Dify has no firewall of its own, so keep the ports private and keep your secrets out of sight.
| Control | How |
|---|---|
| Secrets | SECRET_KEY and the two tokens in .env, chmod 600 .env, never committed |
| Ports | Web, SSL and plugin debug ports on 127.0.0.1 only, or no host port behind Traefik |
| Ollama | Port 11434 closed to the outside in the firewall |
| First setup | Finish /install before anyone else can reach the page |
| Published apps | A published web app is open to anyone who can reach its URL, so protect the address |
| API keys | Create keys per app and call them from your own backend, never from browser code |
| Logs | Do not leave debug logging on, it can contain prompts and answers |
Status tests:
docker compose ps -a docker compose logs --tail=100 api worker plugin_daemon nginx curl -I http://localhost:8080/
Troubleshooting#
| Problem | Solution |
|---|---|
| Port is already allocated on start | Something uses your port. Change EXPOSE_NGINX_PORT in .env and run docker compose up -d again |
| Ollama connection refused in Dify | Check ollama list on the host, OLLAMA_HOST, and restart Ollama after changing it. On Linux make sure the extra_hosts override is applied |
| Plugin cannot install | Run docker compose logs --tail=200 plugin_daemon api. Check internet access and do not turn off signature checking just to get past it |
| A container restarts again and again | Read its log with docker compose logs --tail=100 <service>. For local_sandbox or agent_backend check the token in .env |
| Model runs out of memory | Pick a smaller model, shorten the context, or set OLLAMA_NUM_PARALLEL=1 |
| HTTP 503 from Ollama | It is overloaded. Reduce parallel requests |
| 401 after you changed the domain | Update the console and app URLs and the CORS settings in .env, then restart |
| Indexing is slow | Check ollama ps to see if the embedding model runs on the GPU or the CPU |
Known bugs#
- Tool-server calls in Chatflow apps failed in 1.18.0 (GitHub issue 44635, closed on 24 October; check whether your release includes the fix), and removing and re-adding such a tool can crash an Agent node (issue 44742, still open on 26 October). Not relevant to this guide, but check them if you add such tools later, or use 1.17.1.
- GitHub's "latest release" API returned 1.17.1 for a while although 1.18.0 is marked Latest on the release page. Always use the explicit tag.
FileNotFoundError: File not found#
This shows up when Dify cannot decrypt saved credentials because its storage keys are missing. Restore the storage folder from a backup. The documented key reset is permanent and wipes the saved model and tool credentials of every workspace, so only use it as a last resort.
FATAL: no pg_hba.conf entry for host#
PostgreSQL refuses a connection from a client. Review the database access rules and TLS settings. Do not paste in a rule that trusts every host.
Useful commands:
docker compose ps -a docker compose logs --tail=200 api docker compose logs --tail=200 plugin_daemon docker stats --no-stream ollama ps
Ollama logs: journalctl -u ollama on Linux, ~/.ollama/logs/server.log on Mac, %LOCALAPPDATA%\Ollama on Windows.
Updates and backups#
Back up first with the stack stopped, then follow the notes of the target release.
Dify upgrade steps change from release to release. Always read the release notes of the new tag. In general:
cd ~/docker_volumes/dify/source/docker docker compose stop -t -1 git fetch --tags git checkout <new-tag> git diff 1.18.0 <new-tag> -- .env.example envs/ docker compose pull docker compose up -d
Compare the new .env.example with your .env and copy over new settings by hand. Never overwrite .env with the example. Database migrations run on start while MIGRATION_ENABLED=true. The 1.18.0 notes also say: update the API, workers, agent backend and agent runtime together, and never change PASSWORD_PEPPER on an existing install.
A cold backup script (Linux, Mac and WSL):
#!/usr/bin/env bash set -euo pipefail cd ~/docker_volumes/dify/source/docker docker compose stop -t -1 B=~/docker_volumes/dify/backups/$(date +%Y%m%d-%H%M%S) mkdir -p "$B"; chmod 700 "$B" sudo tar --numeric-owner -czpf "$B/volumes.tar.gz" volumes tar -czpf "$B/config.tar.gz" .env docker-compose.override.yaml 2>/dev/null || tar -czpf "$B/config.tar.gz" .env git describe --tags --always > "$B/release.txt" docker compose up -d
Schedule it with cron at 03:00 on Linux and Mac (0 3 * * * /path/to/dify-backup.sh; the sudo needs a passwordless rule or run it as root). On Windows, create a Task Scheduler task that runs wsl -e /home/<you>/dify-backup.sh.
Restore test: stop the stack, check out the tag in release.txt, extract volumes.tar.gz and config.tar.gz back into docker/, start, and sign in. Try this once on a spare machine before you trust it. An app export (DSL file) is not a backup: it leaves out logs and knowledge bases.
Performance and resources#
Most slowness comes from the model, not from Dify.
- Ollama keeps a model in memory for five minutes after use. Longer keep-alive saves reloads but uses memory.
- Memory grows with context length and with parallel requests. Start with
OLLAMA_NUM_PARALLEL=1andOLLAMA_MAX_LOADED_MODELS=1, then measure. ollama psshows whether a model runs on the CPU, the GPU or both.- On a small host, lower
CELERY_WORKER_AMOUNTin.env(default 4). - Tune retrieval before you change the model. Check that the right chunk was found, that the score threshold did not drop it, and that the model really received it.
- Watch the containers with
docker stats --no-stream.
FAQ#
Can this run without a GPU? Yes. Ollama also runs on the CPU, only slower. Use a small model.
Is it completely offline? Not by default. The first start downloads images and the Ollama plugin, and Dify checks for updates. Once everything is installed, your documents and questions stay local with the Ollama models. A fully offline setup needs its own audit of every plugin.
Why do I need two models? The embedding model finds the right pieces of your documents. The chat model writes the answer from them.
Can I move it to another computer? Yes, but move everything: the docker/volumes folder, your .env and the matching release tag. The repository alone is not enough.
Do citations mean the answer is correct? No. They show where the answer came from, so you can check it. Keep testing with questions you know the answer to.
Sources#
- Dify 1.18.0 release notes and upgrade guide (read on 2026-10-26)
- Dify 1.18.0 docker-compose.yaml and .env.example (read on 2026-10-26)
- Dify Docker Compose deployment docs (read on 2026-10-26)
- Dify knowledge base, chunking and retrieval docs (read on 2026-10-26)
- Ollama FAQ and quickstart (read on 2026-10-26)
- Dify GitHub issues 44635 and 44742 (read on 2026-10-26)
Checked against the official docs and the 1.18.0 Compose file on 2026-10-26. Not run end to end by DIY Lab Hub.