Self-Host Dify with Docker Compose, HTTPS & Persistent Storage

Updated on Aug 24, 2026
Mila H
7 MINS READ
Table of Contents
self-host Dify with Docker Compose

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:

Bash
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:

Bash
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:

Bash
cp .env.example .env

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:

Bash
openssl rand -base64 42

Copy the output and keep it in a safe place.

Open the .env file with your desired text editor:

Bash
nano .env

In the file, look for the line below and paste your secret key:

Bash
SECRET_KEY=paste_the_generated_value_here

Also, find these lines and fill them with your domain name:

Bash
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:

Bash
nano .env
Bash
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:

Bash
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:

Bash
docker compose --profile certbot up --force-recreate -d

Start the Dify stack with certbot

Now issue the certificate itself. Run the certbot container's helper script:

Bash
docker compose exec -it certbot /bin/sh /update-cert.sh

You must get:

Bash
Successfully received certificate.Certificate is saved at: ...

Once the certificate is issued, go back to .env and turn on HTTPS:

Bash
# in .envNGINX_HTTPS_ENABLED=true

Then, use the command below to reload only Nginx:

Bash
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:

Bash
https://yourdomain.com

You must see the setting up an admin account screen. Create your Dify admin account and click Set up.

Dify admin account setup

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

Dify's Home / Templates

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:

SQL
CREATE DATABASE dify;CREATE DATABASE dify_plugin;CREATE USER dify_user WITH ENCRYPTED PASSWORD 'your_strong_password';GRANT ALL PRIVILEGES ON DATABASE dify TO dify_user;GRANT ALL PRIVILEGES ON DATABASE dify_plugin TO dify_user;

Then edit .env on your Dify server:

Bash
nano .env

Update the database block:

Bash
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:

Bash
# in .env, change this line: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:

Bash
nano .env

Set:

Bash
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:

Bash
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.

Dify Model Providers

Install an integration on Dify

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

Add your API key or model in Dify integrations

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:

Bash
docker compose up -d

Watch the logs to confirm every service starts cleanly and database migrations finish:

Bash
docker compose logs -f

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:

Bash
docker compose ps

Keep Your Dify Deployment Updated

To update a server where you self-host Dify with Docker Compose, pull the newest release and rebuild:

Bash
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.

No, you can use your server's IP address, but a domain is required to get a valid Let's Encrypt HTTPS certificate.

No, PostgreSQL is required by default to store app, user, and plugin data.

At least 4GB for testing; production setups with several concurrent users usually need 16GB or more.