By the end of this hour you should have a browser open on Dify Studio – a self-hosted visual LLM builder – dragging LLM, knowledge, and agent nodes on a canvas, with your own API keys and data staying on your box. We’re pinning Dify 1.17.1 (community release, 10 Sep 2026) and using the official Docker Compose path only.
LangGenius ships Dify as an open-source stack for agentic workflows, RAG, and model orchestration. Cloud is fine if you want zero ops. This write-up is for keeping the full stack on hardware you control. Two anchors: the langgenius/dify repo (~157k stars as of late Sep 2026) and the Docker Compose quick start.
What you need before the first pull
Docs floor: ≥2 CPU cores and ≥4 GiB RAM. Once embeddings and agents wake up, that floor feels thin – budget 4 cores and 8 GiB+ if both run together. macOS Docker VM: set at least 2 vCPU / 8 GiB or the install often face-plants. Engine 19.03+ and Compose 2.24.0+ (docker compose version). Disk isn’t fixed in the docs; images + Weaviate + uploads grow fast, so leave headroom on SSD.
On Windows, keep the clone and bind mounts inside the Linux filesystem, not /mnt/c – NTFS bind mounts cause odd volume errors per the docs. Have git ready. curl + jq only matter if you use the dynamic latest-tag one-liner; pinning 1.17.1 skips both.
Get the 1.17.1 source (not floating main)
Main moves. This guide matches the 1.17.1 tag only.
# Dynamic latest tag (needs git, curl, jq)
git clone --branch "$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r .tag_name)" https://github.com/langgenius/dify.git
# Pin when API/jq fails with "Remote branch null"
git clone --branch 1.17.1 https://github.com/langgenius/dify.git
cd dify/docker
Turns out the dynamic form dies with fatal: Remote branch null not found whenever jq/curl is missing or GitHub API is blocked – official quick-start FAQ territory. Use the explicit 1.17.1 line and move on.
Install the visual LLM builder stack
From dify/docker:
- Copy env defaults:
cp .env.example .env - Internet-facing box? Set secrets before first boot:
# Linux sed -i "s|^SECRET_KEY=.*|SECRET_KEY=$(openssl rand -base64 42)|" .env # Empty SECRET_KEY is allowed: Dify can auto-write a persistent key under storage (docker README / env docs) - Storage ownership before
up– containers run as non-root (difyuser). Community + maintainer guidance lands on UID/GID 1001:mkdir -p ./volumes/app/storage sudo chown -R 1001:1001 ./volumes/app/storage # some hosts also need chmod -R 770 on that path - Check Compose:
docker compose version→ 2.24.0 or newer - Launch:
docker compose up -d
As of the Aug 2026 quick-start page: seven core services – api, api_websocket, worker, worker_beat, web, plugin_daemon, agent_backend – plus Weaviate, Postgres, Redis, Nginx, and sandboxes. First pull is multi-GB. Let it finish.
Pro tip: Port 80 busy? Remap Nginx in compose (e.g.
"8080:80") or stop host Apache/Nginx beforeup. Change only the browser URL and skipCONSOLE_*/APP_*env vars and you’ve built your own CORS maze – official Docker issues list calls this out.
First-time configuration that actually matters
Open http://localhost/install (or http://YOUR_SERVER_IP/install). Create admin. App lives at http://localhost after that.
After login, bare minimum: Settings → Model Providers → one LLM (plus an embedding model if knowledge bases matter). No provider, no useful canvas – just an empty visual LLM builder.
Default vector store is Weaviate. Keep it on day one unless you already run something else; swapping stores is an env project, not an install step. Public URL? Fill CONSOLE_API_URL, CONSOLE_WEB_URL, SERVICE_API_URL, and CORS allow-origins in .env, then docker compose down && docker compose up -d.
Verify the install works
docker compose ps
# long-running services: Up or healthy
# one-shot init tasks may show Exited - expected
docker compose logs api --tail 80
# migrations OK, not crash loops
Studio loads? Create a blank Chatflow/Workflow, drop an LLM node, save. That’s the real smoke test. API key against a new app is optional. UI infinite-spin + CORS usually means public URL env vars don’t match how you’re browsing – fix the vars.
Ever notice how “all green in docker compose ps” still doesn’t mean retrieval works? Process health ≠ index health. That gap matters the moment upgrades enter the chat.
Common install errors and fixes
- PermissionDenied on
privkeys/.../private.pem– non-root containers. If you skipped the chown above:chown -R 1001:1001 ./volumes/app/storage, make sure that path is writable, restart api. Same class of failure as GitHub discussion traffic after the non-root shift. - Port 80 in use – remap compose or free the host port (Docker issues docs).
- 502 Bad Gateway – Nginx holding stale upstream IPs after recreate. Prefer service DNS names; container IPs change on restart (official troubleshooting).
- Storage / init flakes on first boot – wait until Postgres shows healthy, then bounce api if it raced the DB.
Upgrade path, Weaviate trap, and uninstall
Fresh 1.17.1: empty Weaviate volume boots on 1.39.2. No ladder.
The catch is existing bundled-Weaviate installs. The 1.17.1 release warns hard: bundled server jumps 1.27.0 → 1.39.2 (twelve minors). Skipping is unsupported and can silently wreck vector search. Official path is the Weaviate server upgrade path: backup ./volumes/weaviate with cp -a, stop app workers, step image tags minor-by-minor (registry moves toward cr.weaviate.io/...), always docker compose stop -t -1 weaviate – never hard kill. Hard stops leave HNSW commit logs incomplete: object counts look fine, near_vector misses rows, no loud error. After each rung, check version + counts + sync. Only then checkout 1.17.1, re-apply .env customizations, docker compose pull && up -d.
# After Weaviate is already on 1.39.2
cd dify/docker
docker compose down
git fetch --tags && git checkout 1.17.1
# diff .env.example → re-apply secrets/URLs
docker compose pull
docker compose up -d
Uninstall / cleanup (Docker Compose CLI):
cd dify/docker
docker compose down # containers + networks; volumes kept
docker compose down -v # also wipes named volumes → DB/vector/uploads gone
cd ../.. && rm -rf dify # optional tree cleanup
docker image prune -f
Back up docker/volumes and .env before any -v or major upgrade. No recycle bin.
Is a twelve-rung Weaviate ladder overkill for a side project? Maybe – but the failure mode is “search looks healthy until it isn’t,” which beats an afternoon of careful stops.
FAQ
Is Docker Compose the only way to run this visual LLM builder?
No. It’s the path the docs optimize for. Other deploy styles exist; Compose is still the least surprising first production-ish box.
I upgraded to 1.17.1 and knowledge retrieval returned empty – what now?
Bundled Weaviate and a straight jump from an older minor? Assume the index migration never ran. Restore the Weaviate volume backup if you have one, walk the official minor ladder with stop -t -1, then bring 1.17.1 up. Object counts can look perfect while vector search misses rows after a hard kill – re-index the damaged dataset in Dify if the graph is already toast. Brand-new empty volumes skip this entirely.
Must I set SECRET_KEY in .env?
Local experiments: empty is fine; Dify can persist an auto-generated key in storage. Anything shared or exposed: openssl rand -base64 42 before first start. Change it later and you log everyone out – encrypted provider OAuth blobs can become unrecoverable. Set it once, on purpose. Details live in the environment variables docs.
Next: pinned clone → cp .env.example .env → chown volumes/app/storage to 1001 → docker compose up -d → /install → first model provider. When Studio loads, ship one tiny Chatflow end-to-end before you invite anyone else in.