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 owndocker-compose.yaml runs each part as a container. This guide runs each on Sevalla as follows:
/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=falseturns 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=falseturns 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:
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 versionprints 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
Create PostgreSQL with pgvector
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.Create Valkey
dify-redis as the Display name, choose the same Data center, and click Create database. Wait until both databases are Ready.Create the bucket
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:Choose the image
Name, place and size it
Set the port and turn HTTP traffic off
dify-api-x7k2m-web.dify-api-x7k2m.svc.cluster.local.-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 exampleacme/dify-gateway, with two files:
Create it from the repository
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.Build from the Dockerfile
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: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:${...} values are references to the variables the database connections added.
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:
Create the migration job
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.Create the worker
Worker as the Name and exec env MODE=worker /bin/bash /entrypoint.sh as the Custom start command, choose S1, and click Create process.Create the scheduler
Beat with the command exec env MODE=beat /bin/bash /entrypoint.sh on S1.Migrateruns 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.Workerruns 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).Beatqueues 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 code137(out of memory) as it started.env MODE=...sets the variable that the image’s/entrypoint.shreads, andexechands the process theSIGTERMthat 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
Deploydify-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.
Open the setup page
https://dify-x7k2m.sevalla.app/install, with the gateway’s domain. Dify asks for the Admin initialization password first: enter INIT_PASSWORD.Create the admin account
Verify the deployment
-
On each application’s Deployments tab, the deployment is Deployed. With the API, Get deployment (
getDeploymentV3) returns"status": "success". -
The
dify-apibuild log showsMigrate has been successful, thenWeb has been deployed successfully,Worker has been deployed successfullyandBeat has been deployed successfully. -
Before you create the admin account, Dify’s setup status answers through the gateway:
After you create it,
stepisfinished. -
The web interface answers:
-
The gateway’s own health check:
-
The migration job’s result, from Get application runtime logs (
getApplicationRuntimeLogsV3), withAPI,HandAPI_APP_IDset as in Deploy with the API:
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.Install the Anthropic plugin
Add your API key
dify-db.Import the workflow
order-summary.yml. Dify creates the Order summary workflow.Publish it and create an API key
https://dify-x7k2m.sevalla.app/v1.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:
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. WithVECTOR_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 answers401with{"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-daemonwith its environment, which includes the database password and the bucket keys.FORCE_VERIFYING_SIGNATUREis on by default, so the daemon refuses plugins without a valid signature. Install only plugins you trust.
Keep the keys
SECRET_KEYsigns 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 updify-dband don’t empty or delete the bucket: without it, every provider must be set up again. PLUGIN_DAEMON_KEY,INNER_API_KEY_FOR_PLUGINand 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: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’sMigrate job runs, and the migration can’t be undone by deploying the old image.
- 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. - Back up
dify-db: on its Backups tab, create a manual backup. See Backups. - On
dify-api’s Settings tab, in the Source section, click Update source, change the tag in Docker image, click Update source, and deploy. TheMigratejob runs the migrations, and the worker and scheduler move to the new image with the same deployment. - Change
dify-web’s tag to the same version and deploy, and changedify-sandboxanddify-plugin-daemonwhen the release’s Compose file uses new tags for them.
Add a custom domain
To serve Dify from your own domain, such asdify.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
400and a message that starts withCannot access repository. curl,jqandopenssl.
Choose the location and the sizes
listClustersV3), List process resource types (listProcessResourceTypesV3) and List database resource types (listDatabaseResourceTypesV3). Pick the location by its region ID, here Frankfurt, Germany:sevalla resources clusters, sevalla resources process-types and sevalla resources db-types. MCP: get_platform_options.Create the databases
createDatabaseV3) twice, then Get database (getDatabaseV3) until both are ready. You choose the passwords, and the API never returns them: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).Create the 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.Create the applications
createApplicationV3) five times. None of them deploys yet: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.Read the hostnames and the domain
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:sevalla apps processes list --app-id $API_APP_ID and sevalla apps domains list --app-id $GATEWAY_APP_ID. MCP: get_application.Configure the web processes
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.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.Connect the services
createApplicationInternalConnectionV3). Through the API, a connection adds no variables; the next step adds them: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.Add the environment variables
createEnvironmentVariableV3), once per variable, all for runtime only. Store SECRET_KEY and INIT_PASSWORD somewhere safe:${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.Create dify-api's other processes
createProcessV3). Every process needs resource_type_id and scaling_strategy, and a job its job_start_policy: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.Build the gateway from its Dockerfile
updateApplicationV3):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.Deploy
triggerDeploymentV3) for the four image applications, then Get deployment (getDeploymentV3) until each is final, then the same for the gateway: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.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, withANTHROPIC_API_KEY set and the workflow saved as order-summary.yml:
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:ADMIN_PASSWORD.Install the Anthropic plugin and add the key
__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.Import, publish and run the workflow