Workload · Building AI apps with Dify

Dify self hosted: build AI apps on a server that is entirely yours

Self-hosting Dify means running its Docker Compose stack on your own machine: download a tagged release, fill .env with secrets generated on the server, keep its web entrance on 127.0.0.1 while you create the admin account through an SSH tunnel, and only then put it on a domain. You do not need a graphics card for Dify itself; the documented floor is two processor cores and 4 GiB of memory. You need one when you also want the models on the same box, served by Ollama.

The short version
  1. No GPU needed for Dify itself; one helps for local models
  2. Generate every secret on the server, never on a command line
  3. Create the admin over SSH before anyone else can see the site
  4. Your own models through the Ollama provider plugin
  5. Free to use commercially, with a multi-tenant exception

What will Dify ask of your server?

Less than most people expect, as long as the models live elsewhere. Dify is the workshop where you assemble chatbots, agents, workflows and retrieval over your own documents; the language models it calls are separate. Its documentation names a floor of two processor cores and 4 GiB of memory, with Docker Compose 2.24.0 or newer. The stack it starts is sizeable all the same: sixteen containers, among them PostgreSQL for app data, Redis, Weaviate for document vectors, two sandboxes that run code, and a plugin daemon that runs model providers.

What you plan to doWhat to getWhy
Build apps on hosted model APIsAn x86 CPU server above the 2-core, 4 GiB floorNothing in Dify itself uses a graphics card
Run small open models yourself (7-8B, 4-bit)A card with 8 GB or more5 GB is the weights alone (our GPU page); the runtime and context need room too
Run a 70B model at 4-bitA card with well over 38 GB, such as 48 GB38 GB is the weights alone; the conversation context needs room too

The docs publish no sizing for large knowledge bases, so plan disk around your own documents: uploaded files, vectors and the database all live under docker/volumes on the server.

Why give Dify a whole server?

Because Dify ends up holding the things you care about most: API keys for your model providers, your company documents, every conversation. On our GPU servers the whole card and the whole machine belong to one customer: no hypervisor, no shared slice of the card, no second tenant whose batch job slows your replies. All of it is yours: each processor core, the full memory and disks, root access, plus the machine's HPE iLO or Dell iDRAC with a remote screen, virtual media and a power switch, so a firewall slip that locks out SSH can still be fixed from the console.

The port is unmetered, so downloading model weights or re-uploading a document library never shows up on the invoice. There is no contract; one month is the default term. If the card you pick already sits in a machine, you can log in roughly 30 minutes after your order is approved; when a card we hold has to be fitted, 4-24 hours; when one has to be bought in, 5-10 working days, and 10-15 for a current-generation accelerator on allocation. Not sure which model fits which card? Our LLM sizing guide walks through it. Only calling hosted APIs? Any x86 CPU-only box from our dedicated servers fits that. Skip the X-Gene ARM cartridge: its two Ubuntu releases, 16.04 and 18.04, are older than anything current Docker supports.

The one trap in the stock setup

Dify's Compose file opens three ports to the whole world the moment it starts: nginx on 80 and 443, and the plugin daemon's debugging port on 5003. That would hand the first-run setup page to whoever finds your IP first. You might expect your firewall to catch it. Often it won't: a published port without an address is open on all of the server's addresses, IPv4 and IPv6, and Docker writes its own packet rules for IPv4 that bypass ufw entirely. On IPv6 ufw still gets a say unless Docker's own IPv6 networking has been switched on.

The fix is a small override file that puts nginx on 127.0.0.1:8080 (443 is no longer published; your own proxy handles HTTPS) and pins the plugin daemon's 5003 to 127.0.0.1, so only the server itself can reach them. Dify's repository even lists docker/docker-compose.override.yaml in its .gitignore, so your file survives upgrades. Use Docker Engine 28 or newer: before that release, machines on the same local network could still reach ports bound to 127.0.0.1 (Moby issue 45610). The !override tag in the file needs Compose 2.24.4 or later, according to Docker's reference on merging Compose files.

Getting it running, step by step

Log in to Ubuntu, become root with sudo -i, and make sure Docker Engine and its Compose plugin came from Docker's own repository. First, the firewall goes up with SSH allowed, IPv6 included; setting IPV6=yes before adding rules matters, because ufw only creates IPv6 twins for rules added after it. Then fetch the current release exactly as Dify's guide does.

sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw default deny incoming
ufw allow 22/tcp
ufw allow proto tcp from 172.16.0.0/12 to any port 11434  # only if Ollama will run here
ufw --force enable
apt-get update && apt-get install -y git curl jq
docker version && docker compose version
cd /opt
git clone --branch "$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r .tag_name)" https://github.com/langgenius/dify.git
cd /opt/dify/docker
cp .env.example .env && chmod 600 .env

Next come the secrets. Out of the box, .env.example carries known passwords for the database and Redis and public sample keys for Weaviate, the sandbox, the plugin daemon and the agent services. Anyone who has read the repository knows them. This snippet generates a fresh random value for each in Python and saves it to .env, which keeps every secret out of your shell history and out of the process list. Do it before Dify starts for the first time.

python3 - <<'PY'
import re, secrets
key = lambda n=32: secrets.token_urlsafe(n)
redis_pw, sandbox_key, weaviate_key = secrets.token_hex(24), key(), key()
values = {
    'SECRET_KEY': key(42),
    'INIT_PASSWORD': key(18),
    'DB_PASSWORD': secrets.token_hex(24),
    'REDIS_PASSWORD': redis_pw,
    'CELERY_BROKER_URL': f'redis://:{redis_pw}@redis:6379/1',
    'SANDBOX_API_KEY': sandbox_key,
    'CODE_EXECUTION_API_KEY': sandbox_key,
    'WEAVIATE_API_KEY': weaviate_key,
    'WEAVIATE_AUTHENTICATION_APIKEY_ALLOWED_KEYS': weaviate_key,
    'PLUGIN_DAEMON_KEY': key(),
    'PLUGIN_DIFY_INNER_API_KEY': key(),
    'DIFY_AGENT_API_TOKEN': key(),
    'DIFY_AGENT_SERVER_SECRET_KEY': key(),
    'DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN': key(),
}
text = open('.env').read()
for name, value in values.items():
    text, hits = re.subn(rf'^{name}=.*$', f'{name}={value}', text, flags=re.M)
    assert hits == 1, name
open('.env', 'w').write(text)
PY

Treat SECRET_KEY as permanent. Dify's docs say changing it later signs everybody out, invalidates file links, and makes credentials that plugins stored through OAuth impossible to recover. The INIT_PASSWORD the script made (24 characters; Dify allows up to 30) is what the setup page will ask you for. grep '^INIT_PASSWORD=' .env prints it.

Last, the override and the first start. When it settles, every container should read Up or healthy except init_permissions, which finishes its job and exits. The ss line should show only 127.0.0.1 addresses.

cat > docker-compose.override.yaml <<'EOF'
# keep Dify's nginx and the plugin debug port on loopback
services:
  nginx:
    ports: !override
      - "127.0.0.1:8080:80"
  plugin_daemon:
    ports: !override
      - "127.0.0.1:5003:5003"
EOF
docker compose up -d
docker compose ps
ss -ltnp | grep -E ':(80|443|5003|8080) '

Your admin account comes first

  1. On your own computer, open a tunnel with your normal login: ssh -N -L 8080:localhost:8080 you@server-ip. Leave it running.
  2. Visit http://localhost:8080/install, type the INIT_PASSWORD, and set up the admin with your email and a strong password.
  3. Sign in. Nobody else can create an account by themselves, because public sign-up is off unless you turn on ALLOW_REGISTER. Invite teammates into your workspace; with no mail server set up, Dify shows the invite link for you to send.
  4. Forgot the admin password later? From /opt/dify/docker, docker compose exec api flask reset-password sets a new one.

Opening it to your team

With the admin in place, install a reverse proxy on the server (nginx with Let's Encrypt is the usual pair) that serves your domain over HTTPS and forwards to 127.0.0.1:8080, then let web traffic in: ufw allow 80,443/tcp. Two things from Dify's docs keep it working behind your proxy: forward WebSocket upgrades on /socket.io/ so collaborative editing works, and send X-Forwarded-For. Our own advice on top: allow long-lived requests and big uploads to match Dify's own nginx, which streams replies for up to an hour and accepts uploads up to 100 MB.

Tell Dify its new address in .env, then restart with docker compose down followed by docker compose up -d:

  • https://ai.example.com for CONSOLE_WEB_URL, CONSOLE_API_URL, APP_WEB_URL, APP_API_URL, SERVICE_API_URL and TRIGGER_URL
  • wss://ai.example.com for NEXT_PUBLIC_SOCKET_URL
  • your domain in place of localhost inside ENDPOINT_URL_TEMPLATE

One more, new with version 1.18.0: Dify limits sign-in and password-reset emails per visitor address, and behind a proxy everyone looks like the proxy. Find the address your proxy really uses in docker compose logs nginx, put it in docker/nginx/conf.d/real-ip.conf as set_real_ip_from, add real_ip_recursive on; plus a real_ip_header line naming X-Forwarded-For, and reload Dify's nginx.

Bringing your own models with Ollama

Running models next to Dify keeps prompts and documents on hardware you control. Set up Ollama on the server itself with our Ollama guide. Out of the box it answers only on 127.0.0.1, and Dify's troubleshooting page points out that a container cannot reach the host that way. That is what the 11434 rule in the first code block is for. It looks only at where a connection comes from: connections from 172.16.0.0/12, the private range Docker's bridges use, get in, and anything from a public address is still refused by the default deny rule. Once it is in place, set OLLAMA_HOST=0.0.0.0:11434 for the Ollama service. Keep that order: Ollama doesn't ask callers for any password, so the firewall is its only gate.

Inside Dify, go to Integrations > Model Provider, install Ollama from the Marketplace, and add your model. For the base URL, the plugin's examples show host.docker.internal or a LAN address, which don't fit a plain Linux machine; what we suggest instead is the gateway address of the docker_default network (run docker network inspect docker_default to see it) followed by :11434. Want a simple chat screen on the same models as well? Open WebUI connects to that Ollama. If your automations are mostly about moving data between apps, n8n may suit them better.

Keeping it backed up and current

Dify's docs say a Compose install is backed up by copying docker/volumes. Add .env to that: restoring without the original SECRET_KEY signs everybody out, breaks file links and loses the OAuth credentials your plugins stored. Stop Dify for a clean copy (docker compose down), archive with umask 077; tar -C /opt/dify/docker -czf /root/dify-backup-$(date +%F).tgz .env docker-compose.override.yaml volumes, start it again with docker compose up -d, and move the archive to storage away from this server. It contains all your secrets, so guard it like a password.

For updates, Dify's docs ask for two things: read the upgrade notes of the version you're moving to (GitHub releases page), and check your .env against .env.example. Everything else here is how we would do it: take a backup, git fetch --tags, git checkout the new version, copy across any variable that is new in .env.example, run docker compose pull for the new images, then docker compose up -d to bring everything back. Then repeat the ss check; new releases sometimes add containers with ports of their own.

What the licence lets you do

Its licence, the Dify Open Source License, is "a modified version of the Apache License 2.0". "Dify may be utilized commercially", it says, and it names two examples: powering other software from behind the scenes, and serving companies as their platform for building applications. Two cases need a commercial licence from its makers: running a multi-tenant service, where "one tenant corresponds to one workspace", and removing or changing the logo or copyright notice in the frontend, which for Docker users means the "web" image. Read the whole licence before you sell anything built on it.

  • Hosting Dify workspaces for paying clients is multi-tenant use: talk to Dify about a licence first.
  • Kubernetes high availability, SSO and managing many workspaces are offered with Dify Enterprise, per its docs; this guide sets up the Community Edition.
  • One server can fail on its own, with nothing to take over. If downtime would really hurt, keep a second machine ready and practise restoring your backup onto it.

Questions people ask

Can I run Dify without a graphics card?
Yes. Dify runs on processors alone, and it wants two cores and 4 GiB of memory at minimum. You only need a card if you want to run the language models on the same server rather than calling a hosted API.
Is the setup page exposed while I install?
Not if you follow this guide. The override keeps Dify's web entrance on 127.0.0.1, so only your SSH tunnel can reach the setup page, and INIT_PASSWORD adds a second lock. Open the domain once your admin exists.
Which models can I connect?
Any provider you install under Integrations, from hosted APIs with your own keys to local models served by Ollama on the same machine. Each provider is a plugin, and Dify checks the credentials before the models appear in your apps.
Can I offer Dify to my own customers?
You can build commercial apps on it and use it as a backend for them. Running Dify as a shared service where customers get their own workspaces counts as multi-tenant under its licence, and that needs written permission from Dify.
Does self-hosted Dify have single sign-on?
Dify's docs list SSO among the Enterprise features. The Community Edition can show GitHub and Google sign-in buttons via ENABLE_SOCIAL_OAUTH_LOGIN, but only once you register your own OAuth app credentials with those services.
What exactly should I back up?
The docker/volumes folder, which holds the database, uploaded files, vectors and plugins, plus your .env and override file. Shut Dify down while you copy so the database is consistent, then store the archive somewhere other than the server.

Give your AI apps a server of their own

Pick a card for the models you want to run locally, or none at all; the whole machine is yours either way.

Find a server for Dify

Read next