Dify upgrades from versions before 1.15

Older self-hosted Dify installations can fail model API calls after a version jump because of legacy model type values. This page explains the upgrade path, the data migration step, and how to test provider credentials afterwards.

On this page

Why Dify upgrades from versions before 1.15 need a planned path

If your self-hosted Dify instance predates 1.15 and its model providers have stopped responding, the cause is most likely legacy model type values stored in your database, and the fix is a planned, release-by-release upgrade rather than a jump straight to the newest image. Older installations carry these legacy values, and they make model API requests fail. Pulling a current image does not clear them on its own, so the upgrade has to pass through the releases that handle the data change in the order their own upgrade guides describe.

Minor releases arrive roughly monthly, which means a gap of several versions can accumulate quickly. Dify 1.16.0 was released on July 17, 2026, 1.17.0 on August 25, 2026, and 1.17.1 on September 10, 2026. The releases handle the migration differently: 1.16.1 and 1.17.0 required a manual flask data-migrate legacy-model-types step, while 1.17.1 runs that step inside flask db upgrade. Before you choose a target, confirm the current release list and each upgrade guide on the Dify releases page, since the steps for your starting version depend on which releases you pass through.

Treat the upgrade as a security change as well as a functional one. The Dify 1.16.1 release notes describe agent sandbox network isolation through a Squid proxy, token-based API authentication between services and sandboxed Jinja2 template rendering. Those controls only apply once you are running the release that includes them, so an instance that stays on an older version keeps the older security behavior. Plan a change window, a backup of the database and volumes, and a rollback path before the first step, and confirm that your custom docker-compose.yaml edits and new .env variables survive each release.

What changes across the 1.15 to 1.17.1 releases

This table compares what each change means for an installation older than 1.15 and what you must do at each step. Read it top to bottom as a sequence, because the legacy model type fix determines whether provider credentials work again after the jump. The main takeaway is that the migration step moved from a manual command in 1.16.1 and 1.17.0 to an automatic run in 1.17.1, so the exact release you target changes your runbook.

ChangeImpact on an older instanceAction needed
Legacy model type valuesInstallations older than 1.15 carry legacy model type values that make model API requests fail, so providers appear configured but calls break.Confirm your starting version, then plan to apply the legacy model type migration before testing any model provider.
Manual legacy-model-types step (1.16.1 and 1.17.0)The migration does not run on its own in these releases, so provider calls keep failing until an operator runs it.Run the flask data-migrate legacy-model-types command after the upgrade, then confirm the command completed in the logs.
Automatic run inside flask db upgrade (1.17.1)The legacy model type migration runs as part of the standard database upgrade step.Check that flask db upgrade completes without errors, and do not assume the migration ran if the step was skipped or interrupted.
Squid proxy network isolation for agent sandbox (1.16.1)Agent sandbox outbound traffic passes through a Squid proxy, which can block calls that previously worked.Test agent tools and sandboxed workflows that reach external endpoints, and review your network allow rules.
Token-based API authentication between services (1.16.1)Internal services authenticate to each other with tokens, so mismatched or missing configuration can stop components from talking.Confirm the new service authentication settings are present in your environment before restarting services.
Sandboxed Jinja2 template rendering (1.16.1)Templates that relied on unrestricted rendering may behave differently or fail inside the sandbox.Retest prompt templates and workflow nodes that use Jinja2 expressions.
New variables in .env.example (each upgrade)Releases can add environment variables that default behavior depends on, and your old .env file will not include them.Diff your .env against .env.example after every upgrade and add any new variables with values your environment requires.

What to back up and inventory before the jump

Before you move an instance older than 1.15 onto a release that runs the legacy model type migration, capture everything you would need to roll back or rebuild. The cards below cover data, secrets, credentials, configuration and a rehearsal copy, and each one feeds the next upgrade step.

Full database and storage backup

Take a complete backup of the PostgreSQL database and the storage volumes that hold uploaded files and knowledge base content before any image changes. The migration that runs inside flask db upgrade rewrites rows that hold model configuration, so a database dump taken on its own is not enough to recover the files behind your knowledge bases. Confirm that you can restore both pieces to the same point in time, and test a restore on a scratch host rather than assuming the backup is usable.

Copy of .env and SECRET_KEY

Store a copy of the .env file somewhere outside the host, with access restricted to the people who run the upgrade. Keep the SECRET_KEY value unchanged, because it signs session cookies and JWTs and encrypts OAuth credentials. Changing it after deployment logs out all users, so treat a new key as a deliberate decision with a communication plan rather than a side effect of copying a fresh .env.example.

Model provider credential inventory

List every model provider configured in the workspace, the account or endpoint each one uses, and who owns the API key. Legacy model type values are the reason model API requests fail after the jump, so record which model types (for example, chat, embedding or rerank) each provider serves today. Testing these credentials after the upgrade is only meaningful if you know what each one should return before you start.

App to model type map

For each published app and workflow, record which model and model type it calls, including embedding and rerank models used by knowledge bases. Apps that reference a model type the upgrade changes are the first to break, so this map tells you which apps to run as acceptance tests. Export app DSL files as a secondary record, remembering that exports omit third-party API keys and knowledge base content.

docker-compose.yaml customizations

Compare your deployed docker-compose.yaml with the upstream file for your current version and list every local change, such as port mappings, volume paths, extra networks or added services. Upgrades replace the file, so these edits must be reapplied deliberately. Record the reason for each change so you can judge whether it still applies to the target release.

Staging copy for rehearsal

Build a staging instance from the restored backup and run the full upgrade there first, including the migration step, provider credential tests and your acceptance apps. Use the same image tags, environment file and storage type as production so the rehearsal is representative. Record the commands, timings and any manual steps, because the staging run becomes the runbook you execute in production.

How the legacy model type migration runs and why it matters

Installations older than Dify 1.15 store model type values in a legacy format that the newer code no longer reads. Until those values are converted, requests to model APIs fail, so a provider that worked before the jump can suddenly return errors even though its credentials are correct. The conversion is a data change in the metadata database, not a configuration change, which is why it cannot be fixed by editing .env or restarting containers. Treat this as a database migration with the same backup discipline you would apply to any schema change.

The way the conversion is triggered depends on the target version. In Dify 1.16.1 and 1.17.0, operators had to run a manual flask data-migrate legacy-model-types step after the upgrade. In Dify 1.17.1, the same conversion runs inside flask db upgrade, so a team that already runs database migrations as part of its upgrade runbook picks it up without an extra command. The practical risk is a team that copies an old runbook: if it lacks the manual step and the target is 1.16.1 or 1.17.0, provider calls will fail after the upgrade. Confirm the exact commands, their order and any expected output in the release notes for your target version before you schedule the change.

Whichever path applies, verify the outcome rather than assuming it. Check the migration output for errors, then test each model provider and model type your applications depend on, including embedding and rerank models used by knowledge bases, by making a real request from the console. Record which providers passed. If the migration is manual, confirm the step was run once and recorded in your change log, because rerunning it on a database that is already converted should be checked against the release notes before you do it. Keep the pre-upgrade backup until every provider has passed a test.

How to run the upgrade one release at a time

This sequence moves an older Dify instance to a current release one step at a time so each legacy model type migration runs, is verified, and can be rolled back before the next release is applied. The output is a production instance where every model provider credential and a sample app per model type have been tested.

  1. Back up and snapshot the instance

    The operator who owns the Dify host takes a database dump from PostgreSQL, copies the storage volumes (file storage, the vector store if bundled, and the plugin daemon data), and saves a copy of the current .env file and docker-compose.yaml. Store the backup outside the host and confirm it can be restored into a separate environment. This snapshot is the rollback point for every later step, because rolling back means restoring the database and volumes taken here or before the step in question. Done when the backup restores cleanly into a scratch environment and the operator has recorded where it is stored.

  2. Record the current version and provider status

    Record the running Dify version, the Docker image tags, the Weaviate or other vector store version, and the list of model providers and model types configured in each workspace. Test each provider with a test call on the current version and note which calls already fail, since failures from legacy model type values are the reason for this upgrade. Keep the list of sample apps that use each model type, because you will reuse it as the acceptance check. Done when the inventory lists every provider, its status and the apps that depend on it.

  3. Stage the upgrade in a test copy

    Restore the backup into an isolated copy with the same images, environment file and vector store version, and disconnect it from production model accounts and outbound integrations. Apply the first target release there, using the upgrade guide for that release on the GitHub Releases page. Confirm the copy starts, the web console loads and the inventory from the previous step reproduces the same failures. Done when the staged copy behaves like production on the current version and you have a written upgrade order for the real environment.

  4. Step through each release that needs a manual migration

    For releases 1.16.1 and 1.17.0, the release notes describe a manual flask data-migrate legacy-model-types step that must run after the new code is in place. Apply each release in order, take a fresh backup before each one, run the documented migration command, and then confirm the migration output reports success. Do not skip a release, because later migrations assume earlier ones have completed. Done when each intermediate release starts, the migration reports completion, and the rollback point for that rung is recorded.

  5. Run flask db upgrade and confirm the migration

    On the target release 1.17.1, run flask db upgrade inside the api container, since this release runs the legacy model types migration as part of that command. Read the command output to confirm the migration ran, and then query the model provider records to check that the legacy type values have been replaced. If the output does not show the migration, stop and restore from the backup taken before this step rather than running the command again on partially changed data. Done when the output confirms the migration and the provider records show the expected type values.

  6. Update .env from .env.example and restart

    Compare the existing .env file with the .env.example shipped for the target release, add any new variables, and keep the values you have already set for secrets, database connections and storage. Do not change SECRET_KEY, because changing it logs out all users and can make stored OAuth credentials unusable. Pull the images for the target version, restart the stack with Docker Compose, and check the container health and logs for the api, worker and plugin daemon. Done when all core services report healthy and the console loads with the expected version.

  7. Test each model provider credential

    Using the inventory from the start, open each model provider in the console and run a test call with its saved credential, then record the result, the model name and the provider response. Where a call fails, check whether the credential, the endpoint, the network route through the SSRF proxy, or the legacy type value is the cause before changing anything. Keep the failed provider list open until every entry is resolved or accepted with a named owner. Done when every provider has a recorded passing test call or a documented exception.

  8. Check a sample app per model type and promote

    Run one representative app for each model type in use, covering chat, completion, embedding and rerank where applicable, and confirm the output, workflow nodes and any knowledge base retrieval behave as they did before the upgrade. Once every sample app passes, schedule the production cutover using the staged order, and keep the pre-upgrade backup available until the operator has confirmed the instance in normal use. Done when the sample app results are recorded, the cutover is approved by the system owner, and the rollback path is documented for the production window.

How to test provider credentials after the jump

Providers that answered before the upgrade are the ones most likely to look fine in the console while still failing for published apps. The legacy model type values that make model API requests fail were stored with the configuration, so a provider that appeared healthy may carry old values that only surface when a workflow calls it. Test each provider as the upgraded instance sees it, not as you remember it.

  • Model call from the console: Open the model provider settings, run a test call against each model in use, and confirm a completed response with no error banner.
  • Model call from a published app: Invoke the published app or workflow that depends on each model, since app-level calls use the same stored configuration but a different request path.
  • Log output: Review the api and worker logs for the test window and look for model request failures tied to the provider name.
  • Evidence to keep: Record the Dify version, provider and model names, test timestamps, app names and the log excerpts with their request outcomes.

Before closing the item, confirm that every model in your inventory has a passing console test and a passing app test.

What makes this upgrade take longer

This table lists the factors that lengthen an upgrade from a Dify version older than 1.15, and the direction each one pushes the work. Read it to see which conditions in your own instance deserve planning time before you schedule a window. Each driver scales with your environment, so confirm your own counts before estimating effort.

DriverWhy it changes effortHow to keep it down
Model providers and credentials in useEach provider with saved credentials must be tested after the jump, and every legacy model type value must be checked against itInventory providers and credentials first, and retire unused ones before the upgrade
Apps and workflows using legacy model typesMore apps and workflows referencing older model types means more places to verify that model calls succeed after migrationExport a list of apps by model, and test a representative app for each provider
Custom docker-compose.yaml changesLocal edits must be reapplied to the new file, and each one needs review against the target releaseKeep overrides in a separate file where possible, and document each change
External PostgreSQL or object storageExternal services add backup, snapshot and access steps, and their configuration must match the target releaseConfirm connection settings and credentials for each external service before starting
Data volume sizeLarger databases and file stores take longer to back up, restore and migrate, and they raise the cost of a rollbackClean up old logs and unused knowledge base data where your retention policy allows
Number of intermediate releasesEach release can add migration steps or new variables, so more releases between your version and the target means more checksRead the release notes for every intermediate version and confirm the target in the releases list
Staging environment availabilityWithout a staging copy, the first real run of the migration happens on production dataRestore a recent backup to a separate host and run the full upgrade there first

Questions teams ask before upgrading an older Dify instance

How much does an upgrade from a Dify version before 1.15 cost?

The cost follows from a written scope, and DeployOpen does not publish a price list, so a quote is the only reliable figure. The main cost drivers are the number of Dify instances, how many model providers and plugins are in use, whether the deployment uses the bundled Weaviate or an external vector store, how many apps and knowledge bases must be checked afterward, and whether the environment is air-gapped. Ask the quote to separate the one-time work (assessment, backup and staging rehearsal, the upgrade itself, provider testing) from anything that recurs, such as ongoing support or a retainer for later minor releases. Ask which items are fixed and which are priced per unit of additional work.

How long does an upgrade from before 1.15 take?

Duration depends on how far the instance is behind, how many version steps the path requires, the size of the database and file storage, and how long the client's test window is. Minor releases have been arriving roughly monthly, so an instance that has fallen several releases behind has more steps and more checks to run. Other drivers are the number of model providers that must be retested, custom docker-compose.yaml changes that need to be reapplied, and approval cycles for change windows. Ask for a schedule with a staging rehearsal before any production work, and confirm the window needed for user testing.

What does my team need to provide for the upgrade?

The work needs a few things from your side before it starts. You need administrative access to the host running Docker Compose, the database, and the file storage, plus read access to the current .env file and any customized compose files. You also need a current backup of the metadata database and storage volumes, verified by a restore test, and the provider credentials for every model in use so that API connections can be tested after the jump. Finally, reserve a test window with named business users who can check apps and knowledge base answers. Confirm the backup and access steps with your own change process before scheduling.

Can our in-house team upgrade Dify without outside help?

Yes, if the team has the skills and time, and a Docker Compose deployment is within reach of most platform teams. The work requires Docker and Compose operation, PostgreSQL backup and restore, reading Dify release notes and the upgrade guide for each target version, and running Flask database commands inside the api container. It also requires someone who can test model provider connections and judge whether a knowledge base still returns sensible results. Teams without this mix often underestimate the rollback path and the provider testing. Ask a partner to review your runbook if the upgrade is the first one on this instance.

Is it safe to jump straight to the latest Dify release?

A direct jump is not a safe default, because the upgrade path depends on the version you start from and on the release notes for each step that the migrations assume. Dify's own guidance is to follow the upgrade guide for the target version, back up before every upgrade, and check .env.example for new variables. Several releases have added steps that must run in a specific order, and some changes depend on earlier data migrations. Plan the path as a sequence of tested stops, confirm each stop in staging, and treat the latest release as the final target rather than the first stop.

What happens to apps and knowledge bases during the migration?

Apps and knowledge bases stay in the database and storage that the upgrade reads, so their configuration is kept, but the model references inside them only work after provider settings are migrated and tested. Legacy model type values from installations older than Dify 1.15 cause model API requests to fail, so an app can appear intact while its prompts and workflows return errors. Knowledge base indexes depend on the vector store, so a bundled Weaviate instance or an external store must match the client version the new release expects. Ask for a checklist that tests each app, workflow and knowledge base retrieval after the jump, and keep the pre-upgrade backup until those tests pass.

What the upgrade changes for security and access

Jumping from a pre-1.15 installation to the current line changes more than model connectivity. The 1.16.1 release notes describe three security changes that affect how a self-hosted Dify behaves after the upgrade: agent sandbox network isolation through a Squid proxy, token-based API authentication between services, and sandboxed Jinja2 template rendering. Each one can break or alter a workflow that worked before, so the first task after the jump is to confirm that the new behavior matches your intent rather than assuming the upgrade handled it. Agent sandbox traffic that once reached a destination directly may now be routed or blocked, and template logic that depended on unrestricted rendering may fail.

Two environment variables deserve a deliberate check. SECRET_KEY signs session cookies and JWTs and encrypts OAuth credentials, and the environment reference notes that changing it after deployment logs out all users, so confirm that the value carried over from the old .env is still set and was not regenerated by a template copy. SSRF_PROXY settings route outbound requests through a proxy that blocks internal and private IP ranges. Confirm that the proxy variables still point at your proxy, and that any internal model endpoints, knowledge sources or webhooks your apps call are still reachable under the rules you intend. Compare your .env file with the current environment variable reference and .env.example rather than relying on the values you copied forward.

Re-check your outbound allow lists after the upgrade, because network rules written for the old sandbox and proxy path may no longer match the traffic Dify generates. Record which destinations each model provider, plugin and tool needs, then test them from the same network position the production containers use. Ask your platform team to confirm that egress from the sandbox is limited to those destinations, and keep the results with the change record. Where a check depends on details that the release notes do not spell out, such as exact proxy behavior for a particular plugin, confirm it with the Dify documentation or the maintainers before signing off.

How DeployOpen works on this

DeployOpen plans and delivers self-hosted Dify upgrades inside your own infrastructure, starting with a written scope that names the instance, its current version and the model providers in use. The work is built and tested in a staging copy before production, with the legacy model type migration verified against each provider credential.

How we scope

  • Confirm your current Dify version, deployment method and whether the bundled Weaviate or another vector store is in use.
  • Inventory every model provider, credential and embedding or rerank model that must work after the jump.
  • Agree in writing the target release, the steps covered and the acceptance tests before work starts, since engagements are fixed-scope.

How we build

  • Take and verify a backup of the database, volumes and environment files before any change is made.
  • Run the upgrade in a staging copy, including the legacy model types step where your release requires it.
  • Test each model provider credential, then confirm model API requests succeed and record the results.

How we hand over

  • Document the exact versions, commands, environment variable changes and rollback steps used for your instance.
  • Provide a provider-by-provider test record showing which credentials were checked and what passed.
  • Walk your operators through the production cutover and the restore procedure, so your team can repeat the runbook.

How we support

  • Offer ongoing support, upgrades and continuous testing as a retainer for later minor releases.
  • Review release notes for each new Dify version and state which changes affect your deployment.
  • Confirm the upgrade path with your own environment and vendor documentation before each future jump.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.