Dify is an open-source platform for building AI apps like chatbots, agents, and RAG systems using a visual, drag-and-drop workflow builder instead of writing lots of code. It supports more than 100 LLM providers and lets you deploy to the cloud or self-host it yourself with Docker Compose.
Teams choose to self-host Dify with Docker Compose because it gives full control over data, model provider keys, and infrastructure spend, without depending on Dify's managed cloud.
This guide covers the full setup from a blank server to a fully working instance with HTTPS, persistent volumes, and external PostgreSQL.
Requirements Before You Begin
Before you self-host Dify with Docker Compose, make sure your server and tools are ready:
- Linux server running Ubuntu LTS with at least 2 CPU cores and 4GB RAM.
- Docker Engine 19.03+ and Docker Compose 2.24.0+.
- A domain name with an A record pointing to your server's public IP.
- Ports 80 and 443 open in your firewall or cloud security group.
- API key for at least one model provider, like OpenAI, Anthropic, Azure, or a self-hosted runner like Ollama.
This stack runs 14+ containers at once, so a small VPS can struggle once you get more users. A strong dedicated server gives you dedicated CPU and RAM, so the database, vector store, and workers all have room to run smoothly.
Verify the required tools are installed correctly on your server:
git --versioncurl --versionjq --versiondocker compose version
Now proceed to the following steps to self-host Dify with Docker Compose.
Step 1. Clone the Dify Repository
This is the first step to self-host Dify with Docker Compose. You must pull the latest tagged release, not the unstable main branch. To do this, run the commands below:
git clone --branch "$(curl -s https://api.github.com/repos/langgenius/dify/releases/latest | jq -r .tag_name)" https://github.com/langgenius/dify.gitcd dify/docker
Step 2. Create and Edit the .env File
At this point, you must copy the example file so you have a working environment file to edit:
This .env file controls every setting when you self-host Dify with Docker Compose.
Now you must generate a secret key, because Dify uses it to sign session cookies and encrypt stored credentials.
Use this command to generate the secret key:
Copy the output and keep it in a safe place.
Open the .env file with your desired text editor:
In the file, look for the line below and paste your secret key:
SECRET_KEY=paste_the_generated_value_here
Also, find these lines and fill them with your domain name:
CONSOLE_API_URL=https://yourdomain.comCONSOLE_WEB_URL=https://yourdomain.comSERVICE_API_URL=https://yourdomain.comAPP_API_URL=https://yourdomain.comAPP_WEB_URL=https://yourdomain.comNEXT_PUBLIC_SOCKET_URL=wss://yourdomain.com
Once you are done, save and close the file. If you still need a domain name, you can check PerLod's domain service registration to register and point a domain at your server in minutes.
Step 3. Configure HTTPS With Let's Encrypt and Access Dify Dashboard
A production setup where you self-host Dify with Docker Compose should never run on plain HTTP long-term. The bundled Nginx supports Certbot for automated Let's Encrypt certificates.
Edit the .env file again and set:
NGINX_SERVER_NAME=yourdomain.comNGINX_HTTPS_ENABLED=falseNGINX_SSL_CERT_FILENAME=fullchain.pemNGINX_SSL_CERT_KEY_FILENAME=privkey.pemNGINX_ENABLE_CERTBOT_CHALLENGE=true
Also, you must add these two lines anywhere in the file:
CERTBOT_DOMAIN=yourdomain.comCERTBOT_EMAIL=you@yourdomain.com
Save and close the file. Keep NGINX_HTTPS_ENABLED=false for now, because no certificate exists yet.
Start the stack with the certbot profile so Nginx can respond to the ACME challenge:
docker compose --profile certbot up --force-recreate -d

Now issue the certificate itself. Run the certbot container's helper script:
docker compose exec -it certbot /bin/sh /update-cert.sh
Successfully received certificate.Certificate is saved at: ...
Once the certificate is issued, go back to .env and turn on HTTPS:
Then, use the command below to reload only Nginx:
docker compose --profile certbot up -d --no-deps --force-recreate nginx
Note: If nginx or plugin_daemon keeps restarting after you self-host Dify with Docker Compose, run docker compose ps -a and check that db_postgres actually started. It can silently fail to launch on the first up. Starting it manually with docker compose up -d db_postgres before restarting the full stack usually fixes both errors at once.
Now you open your browser and visit:
You must see the setting up an admin account screen. Create your Dify admin account and click Set up.

You have now accessed Dify's Home / Templates page.

Note: Let's Encrypt certificates expire every 90 days, so keep certbot's renewal cron active, or repeat the update-cert.sh step and reload Nginx manually every few months.
With login working and the console loaded, you've confirmed the core Dify install is successful. Follow the steps below to configure Dify to use an external PostgreSQL database.
Step 4. Point Dify at an External PostgreSQL Database
The default Compose file runs its own db_postgres container, which works fine for quick tests. But to properly self-host Dify with Docker Compose in production, using a separate and managed PostgreSQL instance is safer for backups, patching, and uptime.
First, on your external PostgreSQL server, create the two databases Dify needs. One for the app and one for the plugin daemon:
1CREATE DATABASE dify;2CREATE DATABASE dify_plugin;3CREATE USER dify_user WITH ENCRYPTED PASSWORD 'your_strong_password';4GRANT ALL PRIVILEGES ON DATABASE dify TO dify_user;5GRANT ALL PRIVILEGES ON DATABASE dify_plugin TO dify_user;
Then edit .env on your Dify server:
Update the database block:
DB_USERNAME=dify_userDB_PASSWORD=your_strong_passwordDB_HOST=your-external-postgres-host.example.comDB_PORT=5432DB_DATABASE=difyDB_PLUGIN_DATABASE=dify_pluginSQLALCHEMY_POOL_PRE_PING=true
Setting SQLALCHEMY_POOL_PRE_PING=true matters here, since external connections over a network drop more than a local Docker network link.
Next, remove the bundled db_postgres service from COMPOSE_PROFILES so it doesn't start pointlessly:
COMPOSE_PROFILES=${VECTOR_STORE:-weaviate},collaboration
This tells Docker Compose to skip the local db_postgres container completely, since it's no longer needed. If many users will use Dify at once, also raise POSTGRES_MAX_CONNECTIONS on your external database, so it has enough space for all your workers and Celery tasks.
Step 5. Set Up Persistent Storage
Every stateful service needs a volume that survives container restarts, or you lose data every time you redeploy. By default, STORAGE_TYPE=opendal with OPENDAL_SCHEME=fs and OPENDAL_FS_ROOT=storage stores uploaded files on a local Docker volume.
For a setup where you truly self-host Dify with Docker Compose in production, moving file storage off local disk to S3-compatible object storage removes a single point of failure.
Edit .env:
STORAGE_TYPE=s3S3_ENDPOINT=https://s3.yourregion.amazonaws.comS3_BUCKET_NAME=your-dify-bucketS3_ACCESS_KEY=your-access-keyS3_SECRET_KEY=your-secret-keyS3_REGION=your-region
Weaviate (the default vector store) also saves its data to its own volume using WEAVIATE_PERSISTENCE_DATA_PATH=/var/lib/weaviate, and Redis keeps its cache and Celery data in its own volume too. It's best to leave both as Docker-managed volumes unless you have a good reason to move them.
Step 6. Add Model Provider Secrets
To actually use Dify once it's running, add at least one model provider key. The cleanest way is through the console after login, not .env, but if you want a provider pre-filled at first boot, you can add it directly in .env:
OPENAI_API_KEY=sk-yourkeyhere
Most people skip this and instead add a provider after their first login. Open the Marketplace in the left sidebar, search for your provider (OpenAI, Anthropic, Azure OpenAI, Gemini, or self-hosted runners like Ollama or vLLM), and click Install.


Once installed, go to Integrations > Model Provider in the left sidebar, where you can paste in your API key or add your model.

Step 7. Launch the Full Dify Stack
With your model provider connected and a default model set, Dify is fully configured. Now launch the complete stack and verify every service is running correctly:
Watch the logs to confirm every service starts cleanly and database migrations finish:
The api container runs migrations automatically on first boot since MIGRATION_ENABLED=true by default. Give it a minute or two before checking status.
Check that all containers came up correctly:
Keep Your Dify Deployment Updated
To update a server where you self-host Dify with Docker Compose, pull the newest release and rebuild:
git pulldocker compose pulldocker compose downdocker compose up -d
After updating, always compare your .env.example with your live .env file. New releases often add new variables or change default values, so this helps you catch anything you need to update.
Conclusion
This setup is a strong Dify deployment including proper HTTPS, a database you fully control, and file storage that won't disappear on restart. Once your usage grows, go for a dedicated server, which gives the database, vector store, and background workers enough space that they need to run smoothly.
We hope you enjoy this guide. For more detailed information, check the Official Dify Docker Compose Docs.