Skip to main content
Dify is an open-source platform for building AI applications: chatbots, agents and workflows that you design in a web interface and call over an API. On Sevalla, Dify runs as five applications: four from Dify’s official Docker images (the API with its background workers, the web interface, the code sandbox and the plugin daemon) and a small nginx gateway you build from a Dockerfile, which is the only one with a public domain. A PostgreSQL database with pgvector stores Dify’s data and the knowledge base vectors, Valkey holds the cache and the task queue, and an object storage bucket keeps uploaded files and plugins, so no application needs a disk. The sandbox, which runs the code in Dify’s Code nodes, works without privileged containers: it isolates code with seccomp and chroot, which Sevalla’s containers allow. This guide was tested with Dify 1.18.0, and ends with a workflow that runs Python in the sandbox and calls Claude Haiku 5.5.

At a glance

Environment variables

An application from a Docker image has no build, so every variable is available during runtime only. Hostnames ending in .svc.cluster.local are internal hostnames: each application’s Networking tab shows its own. dify-api: dify-plugin-daemon: dify-web, dify-sandbox and dify:

How Dify maps onto Sevalla

Dify’s own docker-compose.yaml runs each part as a container. This guide runs each on Sevalla as follows: The gateway gives Dify one domain, as Dify’s own nginx does: it routes the API’s paths (/console/api, /api, /v1, /files and others) to dify-api, /e/ to dify-plugin-daemon, and everything else to dify-web. Dify’s sign-in cookies are host-only (they carry the __Host- prefix), so the web interface and the API have to share a domain.

What this setup leaves out

  • The SSRF proxy. Dify’s Compose file sends outgoing requests from the HTTP Request node and the sandbox through a Squid proxy (ssrf_proxy) that blocks private addresses. This guide doesn’t run it. See Security for what that means.
  • Real-time collaboration on workflows (api_websocket), which needs a WebSocket service. ENABLE_COLLABORATION_MODE=false turns it off; each editor saves the workflow as usual.
  • Dify Agent’s shell workspaces (agent_backend, local_sandbox): the images keep this feature off by default.
  • Plugin debugging over port 5003. PLUGIN_REMOTE_INSTALLING_ENABLED=false turns it off; without it, the plugin daemon doesn’t start: see Troubleshoot.

The code sandbox without privileged containers

Sevalla doesn’t run privileged containers, and the sandbox doesn’t need one. dify-sandbox runs each piece of code as an unprivileged user in a chroot with a seccomp filter that allows only the system calls the language needs. In the tests on Sevalla: The workflow at the end of this guide runs Python in a Code node this way.

Before you begin

  • A Sevalla company with a payment method: Dify needs the S1 size for five processes.
  • A GitHub, GitLab or Bitbucket account for the gateway’s repository. See Git providers.
  • OpenSSL on your computer, to generate keys (openssl version prints its version).
  • An Anthropic API key for the workflow at the end. Set a monthly spending limit on the account.

Create the databases and the bucket

1

Create PostgreSQL with pgvector

In the sidebar, click Databases, then click Create database. Choose PostgreSQL and the newest version (tested: 18), and select Enable pgvector extension. Enter dify-db as the Display name, choose a Data center and the Size DB1, then click Create database. You can turn the extension on only when you create a database.
2

Create Valkey

Click Create database again. Choose Valkey and the newest version (tested: 9.0), enter dify-redis as the Display name, choose the same Data center, and click Create database. Wait until both databases are Ready.
3

Create the bucket

In the sidebar, click Object storage, then click Create object storage. Enter dify-files as the Name, pick a Location and click Create object storage. On its Settings tab, note the Endpoint, the Bucket name, the Access key and the Secret key.
dify-api and dify-plugin-daemon share the database: the plugin daemon’s tables (plugins, plugin_installations and others) have names that Dify doesn’t use. Dify’s Compose file gives the plugin daemon a database of its own, dify_plugin, which the daemon creates at startup, but the user of a Sevalla database can’t create databases.

Create the applications

Create four applications from Dify’s images. For each one:
1

Choose the image

In the sidebar, click Applications, then click Create > Application. Under Source, click Docker image, enter the image from the table in Docker image, and keep the Registry credential at None (public image).
2

Name, place and size it

Enter the Name from the table, choose the databases’ Data center, and choose the Size from the table. Click Create application. It opens with the status Not deployed yet.
3

Set the port and turn HTTP traffic off

Click the Networking tab. In Expose HTTP traffic, click Update port, enter the port from the table and click Update port. Then click Disable, and Disable HTTP in the dialog. Note the web process’s internal hostname that the tab shows, for example dify-api-x7k2m-web.dify-api-x7k2m.svc.cluster.local.
The tags are the ones Dify 1.18.0’s Compose file uses; keep them together when you upgrade. The -local variant of the plugin daemon runs plugins as processes in its own container. dify-api needs S1: it uses about 620 MB.

Create the gateway

The gateway is nginx with a configuration that routes each path to the right application. Create a repository, for example acme/dify-gateway, with two files:
Dockerfile
default.conf.template
Push both files, then create the application:
1

Create it from the repository

In the sidebar, click Applications, then click Create > Application. Under Source, click Private repository, choose the Git provider, the Repository and the Branch. Enter the Name dify, choose the same Data center, keep the Size at H1, and click Create application. Note its domain, for example dify-x7k2m.sevalla.app: every URL setting of Dify needs it.
2

Build from the Dockerfile

On the Settings tab, in the Build strategy section, click Update build strategy. Click Dockerfile, keep the Dockerfile path Dockerfile and the Docker context ., then click Update build strategy.

Connect the services

Internal connections open the private network between two services. On each application’s Networking tab, under Connected services, click Add internal connection, choose the Service, and click Add internal connection: A connection between two applications works in both directions, so dify-plugin-daemon reaches dify-api through the connection you added on dify-api; adding it again on the plugin daemon’s side fails with Internal connection between "<id>" and "<id>" already exists. The connections from the databases add DB_URL and REDIS_URL too, which Dify doesn’t read.

Set the environment variables

Generate the keys on your computer and keep a copy in your password manager:
On each application’s Environment variables tab, click Add environment variables, paste the lines into the first key field, replace the example values with yours, and click Add environment variables. The ${...} values are references to the variables the database connections added.
dify-api
dify-plugin-daemon
dify-web
dify-sandbox
dify
dify-api reads the database and Valkey settings under the names the internal connections add, DB_* and REDIS_*, and so does the plugin daemon.

Add the processes

dify-api’s image starts the API by default. Its other roles run from the same image with another start command. On dify-api’s Processes tab:
1

Create the migration job

Click Create process, choose Job, enter Migrate as the Name and exec flask upgrade-db as the Custom start command. Keep the Start policy at Before deployment, choose the Resource S1, and click Create process.
2

Create the worker

Click Create process, choose Background worker, enter Worker as the Name and exec env MODE=worker /bin/bash /entrypoint.sh as the Custom start command, choose S1, and click Create process.
3

Create the scheduler

Create another Background worker named Beat with the command exec env MODE=beat /bin/bash /entrypoint.sh on S1.
  • Migrate runs Dify’s database migrations before each deployment, so a failed migration leaves the running version in place. On an empty database, it took 51 to 88 s in the tests.
  • Worker runs Celery: it indexes knowledge base documents, runs scheduled and queued workflows, and sends mail. The image’s entrypoint starts it with the gevent pool and a concurrency of 1 (CELERY_WORKER_AMOUNT).
  • Beat queues Dify’s periodic tasks: schedule triggers every minute, cleanups and plugin update checks. Keep it at 1 instance, or every task runs twice. On H1, it stopped with exit code 137 (out of memory) as it started.
  • env MODE=... sets the variable that the image’s /entrypoint.sh reads, and exec hands the process the SIGTERM that Sevalla sends at shutdown.

Add the readiness probes

A readiness probe keeps the previous instance serving until the new one answers. On each application’s Processes tab, click ⋯ on the web process, then Update process. Under Health checks, turn on Readiness probe, enter the Path and keep the Port at the web process port. Click Update process.

Deploy Dify

Deploy dify-sandbox, dify-plugin-daemon, dify-api and dify-web first: on each one’s Overview tab, click Deploy now, then Deploy. They can deploy at the same time; in the test, all four were Deployed in under 2 minutes. Then deploy dify. The gateway resolves the other applications’ hostnames when it starts, so deploy it after them.
The first visitor to https://dify-x7k2m.sevalla.app/install can create Dify’s admin account. INIT_PASSWORD protects that page: keep it secret until you’ve created the account.
1

Open the setup page

Open https://dify-x7k2m.sevalla.app/install, with the gateway’s domain. Dify asks for the Admin initialization password first: enter INIT_PASSWORD.
2

Create the admin account

Enter your email address, a name and a password of at least 8 characters with letters and numbers, and continue. Dify signs you in.

Verify the deployment

  1. On each application’s Deployments tab, the deployment is Deployed. With the API, Get deployment (getDeploymentV3) returns "status": "success".
  2. The dify-api build log shows Migrate has been successful, then Web has been deployed successfully, Worker has been deployed successfully and Beat has been deployed successfully.
  3. Before you create the admin account, Dify’s setup status answers through the gateway:
    After you create it, step is finished.
  4. The web interface answers:
  5. The gateway’s own health check:
  6. The migration job’s result, from Get application runtime logs (getApplicationRuntimeLogsV3), with API, H and API_APP_ID set as in Deploy with the API:
In the runtime logs, the worker logs celery@<instance> ready., the scheduler beat: Starting..., and the plugin daemon dify plugin db initialized.

Run a workflow that calls Claude

Dify connects to model providers through plugins, which the plugin daemon downloads from the Dify Marketplace and runs.
1

Install the Anthropic plugin

In Dify, open Plugins, search the Marketplace for Anthropic, and install it. The plugin daemon stores the package in the bucket and starts it.
2

Add your API key

Open Settings > Model Provider, choose Anthropic, and add your Anthropic API key. Dify checks the key with Anthropic, then encrypts it with the workspace’s key and stores it in dify-db.
3

Import the workflow

In Studio, choose Import DSL file and import the YAML below, saved as order-summary.yml. Dify creates the Order summary workflow.
4

Publish it and create an API key

Open the workflow, click Publish, then open API Access and click Create new Secret key. Dify shows the base URL https://dify-x7k2m.sevalla.app/v1.
order-summary.yml
The Total node runs Python in dify-sandbox to add up the order, and the Reply node sends the items and the total to Claude Haiku 5.5, Anthropic’s fastest and cheapest current model; change name under model for another one. The dependencies entry names the plugin the workflow needs. Run the workflow with your API secret key in APP_KEY:
outputs.reply holds Claude’s sentence, for example Thanks for your order! You've purchased 3 Coffee Mugs and 2 Tea packs, for a total of $47.48.; the wording changes from run to run. A run used about 150 tokens and took 1.3 s. With "response_mode": "streaming", Dify sends server-sent events as each node runs and as Claude writes. They reach the client through the gateway, Sevalla’s proxy and Cloudflare as they’re sent:
In the tests, the first event arrived 0.4 to 0.5 s after the request and the last one after 1.7 to 1.9 s, with text_chunk events from Claude in between. The first run after a deployment took 7.5 s to start.

Add a knowledge base

A knowledge base stores your documents as chunks with embeddings, and Dify’s Knowledge Retrieval node and chat apps search it. With VECTOR_STORE=pgvector, the vectors go to dify-db, one table per knowledge base, for example embedding_vector_index_727a920c_a24b_4fa6_bc87_d70acd386a31_nod. Anthropic has no embeddings model, so install a provider that has one, such as the OpenAI plugin with your OpenAI API key, and choose its text-embedding-3-small as the embedding model when you create the knowledge base. Uploaded files go to the bucket under upload_files/, and the Worker process splits, embeds and stores them. In the test, a one-page returns policy was indexed in about 1 s, and the query My mug arrived cracked, what do I do? returned it with a score of 0.464. To add documents from a script, use Dify’s Knowledge API with a knowledge base API key. In Dify 1.18.0, POST /v1/datasets/<id>/document/create-by-file fails with 500; create-by-text, and uploads in the web interface, work.

Security

  • The admin account. Set INIT_PASSWORD, and create the admin account right after the first deployment. Without the initialization password, Dify’s setup API answers 401 with {"error":{"type":"authentication_error","message":"Unauthorized","status":401}}.
  • Code and HTTP requests in workflows. Without the SSRF proxy, a Code node or an HTTP Request node can reach the internet and, on Sevalla’s private network, other services. In the tests, code in the sandbox couldn’t reach a database it had no internal connection to, but it could open connections to other applications in the same company, including ones it had no internal connection to. Let only people you trust edit workflows, and require authentication on every private service, as Dify’s own services do with their keys.
  • Public endpoints that spend money. Every published app’s API and web app call your model providers. Keep app API keys on your servers, not in browsers, and set spending limits with each provider.
  • Plugins run as processes inside dify-plugin-daemon with its environment, which includes the database password and the bucket keys. FORCE_VERIFYING_SIGNATURE is on by default, so the daemon refuses plugins without a valid signature. Install only plugins you trust.

Keep the keys

  • SECRET_KEY signs sign-in sessions and encrypts the OAuth settings of tools and triggers. Changing it signs everyone out, and Dify can no longer read those settings.
  • The bucket holds each workspace’s private key, which decrypts the model provider keys in dify-db. Back up dify-db and don’t empty or delete the bucket: without it, every provider must be set up again.
  • PLUGIN_DAEMON_KEY, INNER_API_KEY_FOR_PLUGIN and the sandbox key are shared secrets between two applications: change both sides together.

Measured resources

Measured on Oct 30, 2026, with the Anthropic and OpenAI plugins installed: Each plugin is a Python process in dify-plugin-daemon: move it to a larger size as you install more. Installing the Anthropic and OpenAI plugins took 19 s. The API’s web process runs Gunicorn with 1 gevent worker and up to 10 connections at a time (SERVER_WORKER_CONNECTIONS), and each streaming answer holds one; raise the variable or the instance count for more parallel users.

Upgrade Dify

Dify migrates its database when a new version’s Migrate job runs, and the migration can’t be undone by deploying the old image.
  1. Read the release notes of every version between yours and the new one on Dify’s GitHub releases, and the image tags in that release’s docker/docker-compose.yaml.
  2. Back up dify-db: on its Backups tab, create a manual backup. See Backups.
  3. On dify-api’s Settings tab, in the Source section, click Update source, change the tag in Docker image, click Update source, and deploy. The Migrate job runs the migrations, and the worker and scheduler move to the new image with the same deployment.
  4. Change dify-web’s tag to the same version and deploy, and change dify-sandbox and dify-plugin-daemon when the release’s Compose file uses new tags for them.
To go back, restore the backup and set the previous tags.

Add a custom domain

To serve Dify from your own domain, such as dify.example.com, add it on the dify application’s Domains tab and create the DNS records Sevalla shows; the gateway must be larger than H1 first, so scale it. Then change every URL variable on dify-api (CONSOLE_WEB_URL, CONSOLE_API_URL, SERVICE_API_URL, APP_WEB_URL, FILES_URL, TRIGGER_URL) and dify-web (CONSOLE_API_URL, APP_API_URL) to https://dify.example.com, and deploy both. Sign-in cookies belong to a domain, so everyone signs in again. See Custom domains.

Troubleshoot

Deploy with the API

These calls create the same deployment without the dashboard. They need:
  • An API key with the Company developer role, or the capabilities to create applications, databases and object storage, in SEVALLA_API_KEY.
  • The gateway’s repository pushed to GitHub, and a GitHub account with access to it connected to Sevalla. If no connected account can read the repository, the step that creates the gateway fails with 400 and a message that starts with Cannot access repository.
  • curl, jq and openssl.
The sequence creates two databases, a bucket and five applications. The API allows 20 creations per hour of each kind per company.
1

Choose the location and the sizes

List clusters (listClustersV3), List process resource types (listProcessResourceTypesV3) and List database resource types (listDatabaseResourceTypesV3). Pick the location by its region ID, here Frankfurt, Germany:
CLI: sevalla resources clusters, sevalla resources process-types and sevalla resources db-types. MCP: get_platform_options.
2

Create the databases

Create database (createDatabaseV3) twice, then Get database (getDatabaseV3) until both are ready. You choose the passwords, and the API never returns them:
CLI: sevalla databases create, but it has no option for the pgvector extension, so create dify-db with the API. MCP: sevalla_api_create with createDatabaseV3 and the same bodies (create_database generates a password it doesn’t return, and Dify needs it).
3

Create the bucket

Create object storage bucket (createObjectStorageV3), then Get object storage bucket (getObjectStorageV3): the create response doesn’t include the keys.
BUCKET holds the endpoint, the bucket name and the keys; don’t print it. CLI: sevalla object-storage create --name dify-files --location weur and sevalla object-storage get $BUCKET_ID. MCP: sevalla_api_create with createObjectStorageV3, then sevalla_api_read with getObjectStorageV3.
4

Create the applications

Create application (createApplicationV3) five times. None of them deploys yet:
CLI: sevalla apps create --name dify-api --cluster $CLUSTER_ID --source dockerImage --docker-image langgenius/dify-api:1.18.0, and --source privateGit --git-type github --repository $REPO_URL --branch $BRANCH for the gateway. MCP: create_application with deploy: false.
5

Read the hostnames and the domain

List processes (listProcessesV3) returns each web process’s ID and internal hostname, and List application domains (listApplicationDomainsV3) the gateway’s domain, about a second after it’s created:
CLI: sevalla apps processes list --app-id $API_APP_ID and sevalla apps domains list --app-id $GATEWAY_APP_ID. MCP: get_application.
6

Configure the web processes

Update process (updateProcessV3) sets each port, turns HTTP traffic off (is_ingress_enabled: false), and sets the size and the readiness probe. Send timeoutSeconds: without it, a probe set through the API waits 120 seconds instead of the dashboard’s 1.
CLI: sevalla apps processes update <process-id> --app-id <app-id> --resource-type $S1_ID sets the size; it has no option for the port, HTTP traffic or the probe. MCP: sevalla_api_update with updateProcessV3 and the same bodies.
7

Connect the services

Create internal connection (createApplicationInternalConnectionV3). Through the API, a connection adds no variables; the next step adds them:
Each call prints true. CLI: sevalla apps internal-connections create $API_APP_ID --target-id $DB_ID --target-type database, once per line. MCP: sevalla_api_create with createApplicationInternalConnectionV3.
8

Add the environment variables

Create environment variable (createEnvironmentVariableV3), once per variable, all for runtime only. Store SECRET_KEY and INIT_PASSWORD somewhere safe:
Each line of output lists the keys added to one application. The single quotes keep ${REDIS_PASSWORD} and the other references for Sevalla to resolve at deployment. CLI: sevalla apps env-vars create <app-id> --key <key> --value <value>, once per variable; it makes each variable available during build and runtime, which makes no difference for an image. MCP: set_environment_variables.
9

Create dify-api's other processes

Create process (createProcessV3). Every process needs resource_type_id and scaling_strategy, and a job its job_start_policy:
CLI: sevalla apps processes create $API_APP_ID --name Worker --type worker --resource-type $S1_ID --command "exec env MODE=worker /bin/bash /entrypoint.sh", and --type job --job-start-policy beforeDeployment for Migrate. MCP: sevalla_api_create with createProcessV3 and the same body.
10

Build the gateway from its Dockerfile

Update application (updateApplicationV3):
CLI: sevalla apps update $GATEWAY_APP_ID --data '{"build_type": "dockerfile", "dockerfile_path": "Dockerfile", "docker_context": "."}'. MCP: sevalla_api_update with updateApplicationV3 and the same body.
11

Deploy

Trigger deployment (triggerDeploymentV3) for the four image applications, then Get deployment (getDeploymentV3) until each is final, then the same for the gateway:
In the test, the four image applications were done after 111 s and the gateway 2 minutes later. On failed, read the build log with Get application deployment logs (getApplicationDeploymentLogsV3), with ?limit=5000 for all of it. CLI: sevalla apps deployments trigger <app-id> and sevalla apps deployments get <deployment-id> --app-id <app-id>. MCP: trigger_deployment and get_deployment.
12

Check Dify

Set up Dify with its console API

The web interface does the first setup through Dify’s console API, so a script can do it too. The console API isn’t one of Dify’s documented public APIs, and it can change between versions; these calls work with Dify 1.18.0. They continue in the same shell, with ANTHROPIC_API_KEY set and the workflow saved as order-summary.yml:
1

Create the admin account

POST /console/api/init checks INIT_PASSWORD and marks the session, POST /console/api/setup creates the account, and POST /console/api/login signs in with the password Base64-encoded. The cookie jar keeps the session:
Use your own email address, and keep ADMIN_PASSWORD.
2

Install the Anthropic plugin and add the key

Console calls send the __Host-csrf_token cookie’s value in X-CSRF-Token. The plugin’s identifier comes from the Marketplace: curl -s https://marketplace.dify.ai/api/v1/plugins/langgenius/anthropic | jq -r .data.plugin.latest_package_identifier prints the current one.
3

Import, publish and run the workflow