AILLMDocker

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.

WhatValue
Dify version1.18.0 (released 2026-10-23, read on 2026-10-26)
Time neededAbout 30 minutes, plus the model downloads
Size and RAMNot measured by us. Dify's own minimum is 2 CPU cores and 4 GiB RAM, and the models come on top
Ports8080 for the web app (we change the default 80); 5003 plugin debugging is published by default, we lock it down
Official docsdocs.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.

How one question travels: you ask in the browser, Dify nginx passes it to the Dify web and API, which finds chunks in the knowledge base (Weaviate and Postgres) and sends the question plus chunks to the plugin daemon, which calls Ollama on your own 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.

SystemWhat to have
LinuxDocker Engine 19.03 or newer, Compose 2.24.0 or newer, git, openssl
WindowsWindows 10 22H2 or newer, Docker Desktop with the WSL2 backend, an Ubuntu WSL distribution. You run the Dify commands inside WSL, not in PowerShell
MacmacOS 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:

Run in terminalbash
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#

Run in terminalbash
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.

Run in terminalpowershell
wsl
Run in terminalbash
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:

Run in terminalbash
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.

Run in terminalbash
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:

Run in terminaltext
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_KEY signs your sessions. Keep it forever: changing it later logs everyone out and can make saved credentials unreadable.
  • DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN is required by the 1.18.0 agent sandbox. If it stays empty, that container will not start.
  • DIFY_AGENT_API_TOKEN replaces a development default that ships in the file.
  • The three EXPOSE_... lines put the web app and the plugin debug port on 127.0.0.1 only. Compose builds the port as 127.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 remove 127.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#

Run in terminalbash
docker compose up -d
docker compose ps -a

Windows (PowerShell)#

In the WSL shell from Step 1 (Docker Desktop must be running):

Run in terminalbash
docker compose up -d
docker compose ps -a

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
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#

Run in terminalbash
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:

Run in terminalbash
sudo systemctl edit ollama.service

Add these lines in the editor, save, then restart:

File contenttext
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Run in terminalbash
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):

File contentyaml
services:
  plugin_daemon:
    extra_hosts:
      - "host.docker.internal:host-gateway"
Run in terminalbash
docker compose up -d

Windows (PowerShell)#

Install Ollama for Windows, then in PowerShell:

Run in terminalpowershell
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:

Run in terminalbash
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:

Run in terminalbash
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.

Run in terminalbash
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:

  1. In Dify, open the plugins area and search the Marketplace for the Ollama model provider. Install it. (This needs internet access once.)
  2. Open the Model Provider settings and add a model from Ollama.
  3. Chat model: name gemma4:e2b, type LLM, base URL http://host.docker.internal:11434.
  4. Embedding model: name embeddinggemma, type Text Embedding, the same base URL.
  5. 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.

  1. 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.
  2. Chunking: choose General mode. A reasonable first try is a \n\n delimiter, 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.
  3. 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.
  4. 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.
  5. Open Studio, create an app, choose Chatbot, and under Context add your knowledge base. Select gemma4:e2b as the model. Turn on Citation and Attribution.
  6. A simple system prompt:
Codetext
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.
  1. 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):

File contentyaml
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:

File contenttext
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>
Run in terminalbash
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.

ControlHow
SecretsSECRET_KEY and the two tokens in .env, chmod 600 .env, never committed
PortsWeb, SSL and plugin debug ports on 127.0.0.1 only, or no host port behind Traefik
OllamaPort 11434 closed to the outside in the firewall
First setupFinish /install before anyone else can reach the page
Published appsA published web app is open to anyone who can reach its URL, so protect the address
API keysCreate keys per app and call them from your own backend, never from browser code
LogsDo not leave debug logging on, it can contain prompts and answers

Status tests:

Run in terminalbash
docker compose ps -a
docker compose logs --tail=100 api worker plugin_daemon nginx
curl -I http://localhost:8080/

Troubleshooting#

ProblemSolution
Port is already allocated on startSomething uses your port. Change EXPOSE_NGINX_PORT in .env and run docker compose up -d again
Ollama connection refused in DifyCheck 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 installRun 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 againRead its log with docker compose logs --tail=100 <service>. For local_sandbox or agent_backend check the token in .env
Model runs out of memoryPick a smaller model, shorten the context, or set OLLAMA_NUM_PARALLEL=1
HTTP 503 from OllamaIt is overloaded. Reduce parallel requests
401 after you changed the domainUpdate the console and app URLs and the CORS settings in .env, then restart
Indexing is slowCheck 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:

Run in terminalbash
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:

Run in terminalbash
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):

File contentbash
#!/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=1 and OLLAMA_MAX_LOADED_MODELS=1, then measure.
  • ollama ps shows whether a model runs on the CPU, the GPU or both.
  • On a small host, lower CELERY_WORKER_AMOUNT in .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#

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.