Deploying Dify on your own infrastructure can be a daunting task, requiring significant DevOps resources and expertise. You'd need to handle infrastructure provisioning, security configurations, integrations with various tools and data sources, and ongoing maintenance and updates – all while ensuring reliability, performance, and cost-effectiveness. This can quickly become a maintenance nightmare, draining your team's time and budget.
With Shakudo, you can seamlessly integrate Dify into your existing data stack within your secure VPC or on-premises environment, with just a few clicks. Our automated DevOps platform handles the entire deployment, integration, and maintenance process, allowing your team to focus on building and launching innovative AI solutions. Shakudo ensures a reliable, high-performance, and cost-optimized experience by creating compatibility across best-of-breed data tools, streamlining workflows, and automating DevOps processes. You gain the power of Dify.AI's advanced LLM application development capabilities without the operational complexities, enabling you to drive real business value from AI initiatives
export KCFG=/path/to/customer-kubeconfig
export KCTX=<customer-kubernetes-context>
export DIFY_NS=<dify-namespace>
export VALKEY_NS=<valkey-namespace>
export RELEASE=<helm-release-name>
export VALKEY_HOST=<valkey-service-name-or-fqdn>
kubectl --kubeconfig "$KCFG" --context "$KCTX" config current-context
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get secret dify-api -o yaml > /tmp/dify-api_backup.yaml
REDIS_HOST and stop therekubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get deploy,pods,svc
kubectl --kubeconfig "$KCFG" --context "$KCTX" get pods,svc,statefulset -A | egrep 'redis|valkey|dify'
export VALKEY_PASSWORD="$(kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$VALKEY_NS" get secret dify-valkey-auth -o jsonpath='{.data.password}' | base64 -d)"
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$VALKEY_NS" exec dify-valkey-0 -- redis-cli -a "$VALKEY_PASSWORD" PING
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$VALKEY_NS" exec dify-valkey-0 -- redis-cli -a "$VALKEY_PASSWORD" INFO keyspace
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" logs deploy/dify-worker --since=30m | egrep 'transport:|results:|Connected to redis://|valkey'
Mixed-state symptom:
transport: points to Redisresults: points to Valkeykubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get deploy,sts,configmap,secret -o yaml | egrep -n 'dify-redis-master|dify-valkey|REDIS_HOST|CELERY_BROKER_URL|CELERY_RESULT_BACKEND'
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get deploy dify-api -o json | jq '{name: .metadata.name, envFrom: (.spec.template.spec.containers[0].envFrom // []), env: (.spec.template.spec.containers[0].env // [])}'
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get deploy dify-worker -o json | jq '{name: .metadata.name, envFrom: (.spec.template.spec.containers[] | select(.name=="worker") | {envFrom, env})}'
for name in dify-api dify-worker; do
echo "== configmap/$name =="
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get configmap "$name" -o json | jq -r '.data | to_entries[]
| select(.key=="CELERY_BROKER_URL" or .key=="CELERY_RESULT_BACKEND" or .key=="REDIS_HOST")
| [.key, (.value|gsub("//:([^@]+)@"; "//:**@"))] | @tsv'
echo "== secret/$name =="
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get secret "$name" -o json | jq -r '.data
| with_entries(.value |= @base64d)
| to_entries[]
| select(.key=="CELERY_BROKER_URL" or .key=="CELERY_RESULT_BACKEND" or .key=="REDIS_PASSWORD")
| if (.key=="REDIS_PASSWORD") then [.key, "<redacted>"]
else [.key, (.value|gsub("//:([^@]+)@"; "//:**@"))] end
| @tsv'
done
If ConfigMaps already show Valkey but Secrets still show Redis for CELERY_BROKER_URL, the environment is still mixed.
Worker transport still points to Redis while results point to Valkey.
ConfigMaps look correct, but Secrets still send broker traffic to Redis.
REDIS_HOST-only fix does not workChanging only REDIS_HOST does not complete the broker cutover.
helm --kubeconfig "$KCFG" --kube-context "$KCTX" -n "$DIFY_NS" get values "$RELEASE" -a > /tmp/${RELEASE}_values_before.yaml
helm --kubeconfig "$KCFG" --kube-context "$KCTX" -n "$DIFY_NS" get manifest "$RELEASE" > /tmp/${RELEASE}_manifest_before.yaml
helm --kubeconfig "$KCFG" --kube-context "$KCTX" -n "$DIFY_NS" history "$RELEASE" > /tmp/${RELEASE}_history_before.txt
for r in deployment/dify-api deployment/dify-worker deployment/dify-plugin-daemon configmap/dify-api configmap/dify-worker configmap/dify-plugin-daemon secret/dify-api secret/dify-worker; do
kind="${r%%/*}"
name="${r##*/}"
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get "$kind" "$name" -o yaml > /tmp/${name}_backup.yaml
done
If your deployment package exposes the correct Helm values, the exact stale paths found in staging were:
externalSecret.api.data.CELERY_BROKER_URLexternalSecret.worker.data.CELERY_BROKER_URLThese should point to Valkey, not Redis.
for name in dify-api dify-worker; do
broker="$(kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get secret "$name" -o json | jq -r '.data.CELERY_RESULT_BACKEND | @base64d')"
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" patch secret "$name" --type merge -p "$(jq -nc --arg v "$broker" '{stringData:{CELERY_BROKER_URL:$v}}')"
done
REDIS_HOST referencesfor name in dify-api dify-worker dify-plugin-daemon; do
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" patch configmap "$name" --type merge -p "$(jq -nc --arg host "$VALKEY_HOST" '{data:{REDIS_HOST:$host}}')"
done
for d in dify-api dify-worker dify-plugin-daemon; do
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" rollout restart deployment "$d"
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" rollout status deployment "$d" --timeout=300s
done
for d in dify-api dify-worker dify-plugin-daemon; do
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" rollout status deployment "$d" --timeout=300s
done
for name in dify-api dify-worker; do
echo "== secret/$name =="
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get secret "$name" -o json | jq -r '.data
| with_entries(.value |= @base64d)
| to_entries[]
| select(.key=="CELERY_BROKER_URL" or .key=="CELERY_RESULT_BACKEND")
| [.key, (.value|gsub("//:([^@]+)@"; "//:**@"))] | @tsv'
done
for name in dify-api dify-worker dify-plugin-daemon; do
echo "== configmap/$name =="
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get configmap "$name" -o json | jq -r '.data | to_entries[]
| select(.key=="CELERY_BROKER_URL" or .key=="CELERY_RESULT_BACKEND" or .key=="REDIS_HOST")
| [.key, (.value|gsub("//:([^@]+)@"; "//:**@"))] | @tsv' || true
done
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" logs deploy/dify-worker --since=30m | egrep 'transport:|results:|Connected to redis://|valkey'
Expected:
transport: points to Valkeyresults: points to Valkeydify-redis-master references remainkubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" get deploy,configmap,secret -o yaml | grep -n 'dify-redis-master' || true
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$VALKEY_NS" exec dify-valkey-0 -- redis-cli -a "$VALKEY_PASSWORD" PING
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$VALKEY_NS" exec dify-valkey-0 -- redis-cli -a "$VALKEY_PASSWORD" INFO keyspace
Validate:
dify-redis-master references remainUse the YAML backups in /tmp.
helm --kubeconfig "$KCFG" --kube-context "$KCTX" -n "$DIFY_NS" rollback "$RELEASE" <previous_revision>
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" rollout status deployment/dify-api --timeout=300s
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" rollout status deployment/dify-worker --timeout=300s
kubectl --kubeconfig "$KCFG" --context "$KCTX" -n "$DIFY_NS" logs deploy/dify-worker --since=30m
REDIS_HOSTexport DIFY_NAMESPACE="dify"
export DIFY_RELEASE="dify"
export DIFY_HOST="<dify-subdomain>.<customer-domain>"
export STORAGE_CLASS="<storage-class>"
kubectl create namespace "$DIFY_NAMESPACE" --dry-run=client -o yaml > namespace.yaml
kubectl -n "$DIFY_NAMESPACE" create secret generic dify-secrets --from-literal=DB_USERNAME="<db-user>" --from-literal=DB_PASSWORD="<db-password>" --from-literal=REDIS_PASSWORD="<redis-or-valkey-password>" --from-literal=SECRET_KEY="<dify-secret-key>" --dry-run=client -o yaml > dify-secrets.yaml
# Store and apply secrets through the approved deployment workflow.
kubectl apply -f namespace.yaml
kubectl apply -f dify-secrets.yaml
cat > dify-values.yaml <<'EOF'
global:
host: <dify-subdomain>.<customer-domain>
ingress:
enabled: true
tls: true
postgresql:
external: true
host: <postgres-host>
database: dify
existingSecret: dify-secrets
redis:
external: true
host: <redis-or-valkey-host>
existingSecret: dify-secrets
objectStorage:
provider: s3
bucket: <dify-bucket>
endpoint: <object-storage-endpoint>
persistence:
storageClass: <storage-class>
secrets:
existingSecret: dify-secrets
EOF
helm upgrade --install "$DIFY_RELEASE" <dify-chart> --namespace "$DIFY_NAMESPACE" --values dify-values.yaml
kubectl -n "$DIFY_NAMESPACE" rollout status deploy/dify-api --timeout=300s
kubectl -n "$DIFY_NAMESPACE" rollout status deploy/dify-web --timeout=300s
kubectl -n "$DIFY_NAMESPACE" rollout status deploy/dify-worker --timeout=300s
kubectl -n "$DIFY_NAMESPACE" get pods
kubectl -n "$DIFY_NAMESPACE" get svc
kubectl -n "$DIFY_NAMESPACE" get ingress
kubectl -n "$DIFY_NAMESPACE" logs deploy/dify-api --tail=100
kubectl -n "$DIFY_NAMESPACE" logs deploy/dify-worker --tail=100
# Validate from Shakudo:
# 1. Log in to Shakudo.
# 2. Open Stack Components.
# 3. Launch Dify.
# 4. Confirm the Dify UI loads through the Shakudo-managed route.
# 5. Confirm admin login and workspace access.
curl -I "https://$DIFY_HOST"
# In the Dify UI:
# - Configure one approved model provider.
# - Create a test chat app.
# - Send a simple prompt.
# - Upload one small approved document to a knowledge base.
# - Ask one document-grounded question.
kubectl -n "$DIFY_NAMESPACE" logs deploy/dify-worker --tail=100
helm -n "$DIFY_NAMESPACE" history "$DIFY_RELEASE"
helm -n "$DIFY_NAMESPACE" rollback "$DIFY_RELEASE" <revision>
# For first-time failed installs, uninstall only after confirming whether PVCs,
# buckets, databases, or secrets must be retained.
helm -n "$DIFY_NAMESPACE" uninstall "$DIFY_RELEASE"