Difyの.env.exampleを毎回目視で見てる人へ:安全に同期するスクリプトを書いた
はじめに
OSS 版の Dify を本番環境で運用していると、バージョンアップのたびに必ず発生する作業があります。それが .env.example の変更内容を既存の .env に反映する作業 です。
Dify の .env.example は 1400 行を超える非常に大きなファイルで、バージョンが上がるたびに新しい環境変数の追加や、既存設定の推奨値変更、非推奨項目の整理などが行われます。
これを毎回目視で確認し、本番環境用に調整された .env に反映するのは、正直かなりの負担です。
実際の運用では、次のような悩みを感じていました。
- 新しい環境変数が追加されているが、見落としてしまう
- 推奨値が変わっているが、本番では変えるべきか判断に迷う
- どこまでが「安全に自動反映してよい変更」なのか分からない
- うっかり本番固有の設定を上書きしてしまいそうで怖い
本記事では、こうした課題を解決するために作成した
「部分同期」に対応した環境変数同期シェルスクリプト を紹介します。

このスクリプトを使うことで以下の運用が可能になります。
- 本番環境のカスタム設定を保持したまま
-
.env.exampleに追加された新規変数だけを安全に取り込み - 変更差分を分かりやすく可視化する
想定読者
この記事は、以下のような方を想定しています。
- OSS 版 Dify を Docker 環境で運用している方
- バージョンアップ作業をできるだけ安全・効率的にしたい方
「Dify の env 管理、正直しんどい…」と感じたことがある方には、特に刺さる内容になっていると思います。
解決策の概要
今回作成した dify-env-sync.sh は、Dify の .env.example と .env を比較しながら、必要な変更だけを安全に反映する ことを目的としたスクリプトです。
dify-env-sync.sh
#!/bin/bash
# ================================================================
# Dify環境変数同期スクリプト
#
# 機能:
# - .env.exampleの最新設定を.envに同期
# - my.envに記載された独自設定値を保持
# - 新しい環境変数の追加
# - 削除された環境変数の検出
# - バックアップファイルの作成
# ================================================================
# set -e # エラー時に即座に終了(デバッグのため一時的にコメントアウト)
# エラーハンドリング関数
handle_error() {
local line_no=$1
local error_code=$2
echo -e "\033[0;31m[ERROR]\033[0m スクリプトエラー: 行 $line_no でエラーコード $error_code"
echo -e "\033[0;31m[ERROR]\033[0m デバッグ情報: 現在の作業ディレクトリ $(pwd)"
exit $error_code
}
# エラートラップの設定
trap 'handle_error ${LINENO} $?' ERR
# 色付き出力のための設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ログ関数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# ファイル存在チェック
check_files() {
log_info "必要なファイルの存在確認中..."
if [[ ! -f ".env.example" ]]; then
log_error ".env.example が見つかりません"
exit 1
fi
if [[ ! -f ".env" ]]; then
log_warning ".env ファイルが存在しません。.env.example をコピーして作成します。"
cp ".env.example" ".env"
log_success ".env ファイルを作成しました"
fi
log_success "必要なファイルが確認されました"
}
# バックアップファイル作成
create_backup() {
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_dir="env-backup"
# バックアップディレクトリが存在しない場合は作成
if [[ ! -d "$backup_dir" ]]; then
mkdir -p "$backup_dir"
log_info "バックアップディレクトリ $backup_dir を作成しました"
fi
if [[ -f ".env" ]]; then
local backup_file="${backup_dir}/.env.backup_${timestamp}"
cp ".env" "$backup_file"
log_success "既存の .env を $backup_file にバックアップしました"
fi
}
# .envと.env.exampleの差分を検出
detect_differences() {
log_info ".env と .env.example の差分を検出中..."
local temp_env="/tmp/env_values_$$"
local temp_example="/tmp/example_values_$$"
# .envの値を取得
> "$temp_env"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
local value=$(echo "$parsed" | cut -d'|' -f2-)
echo "${key}|${value}" >> "$temp_env"
fi
done < .env
# .env.exampleの値を取得
> "$temp_example"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
local value=$(echo "$parsed" | cut -d'|' -f2-)
echo "${key}|${value}" >> "$temp_example"
fi
done < .env.example
# 一時的に差分情報を格納するファイル
local temp_diff="/tmp/env_diff_$$"
> "$temp_diff"
# 差分を検出
local diff_count=0
while read -r example_line; do
local key=$(echo "$example_line" | cut -d'|' -f1)
local example_value=$(echo "$example_line" | cut -d'|' -f2-)
# .envから対応する値を取得
local env_value=$(grep "^${key}|" "$temp_env" 2>/dev/null | cut -d'|' -f2- || echo "")
if [[ -n "$env_value" && "$env_value" != "$example_value" ]]; then
echo "${key}|${env_value}" >> "$temp_diff"
((diff_count++))
fi
done < "$temp_example"
# グローバル変数として差分ファイルパスを保存
declare -g DIFF_FILE="$temp_diff"
# 一時ファイルをクリーンアップ
rm -f "$temp_env" "$temp_example"
if [[ $diff_count -gt 0 ]]; then
log_success "$diff_count 個の環境変数で差分が検出されました"
# 差分の詳細を表示
show_differences_detail
else
log_info "差分は検出されませんでした"
fi
}
# 環境変数のパース関数
parse_env_line() {
local line="$1"
local key=""
local value=""
# 空行やコメント行をスキップ
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && return 1
# = で分割
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
# 先頭・末尾の空白を除去
key=$(echo "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
if [[ -n "$key" ]]; then
echo "$key|$value"
return 0
fi
fi
return 1
}
# 差分の詳細を表示
show_differences_detail() {
log_info ""
log_info "=== 環境変数の差分詳細 ==="
# 一時ファイルを再作成
local temp_env="/tmp/env_values_detail_$$"
local temp_example="/tmp/example_values_detail_$$"
# .envの値を取得
> "$temp_env"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
local value=$(echo "$parsed" | cut -d'|' -f2-)
echo "${key}|${value}" >> "$temp_env"
fi
done < .env
# .env.exampleの値を取得
> "$temp_example"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
local value=$(echo "$parsed" | cut -d'|' -f2-)
echo "${key}|${value}" >> "$temp_example"
fi
done < .env.example
# 差分を表示
local count=1
while read -r example_line; do
local key=$(echo "$example_line" | cut -d'|' -f1)
local example_value=$(echo "$example_line" | cut -d'|' -f2-)
# .envから対応する値を取得
local env_value=$(grep "^${key}|" "$temp_env" 2>/dev/null | cut -d'|' -f2- || echo "")
if [[ -n "$env_value" && "$env_value" != "$example_value" ]]; then
echo ""
echo -e "${YELLOW}[$count] $key${NC}"
echo -e " ${GREEN}.env (現在値)${NC} : ${env_value}"
echo -e " ${BLUE}.env.example (推奨値)${NC}: ${example_value}"
# 値の比較分析
analyze_value_change "$env_value" "$example_value"
((count++))
fi
done < "$temp_example"
# 一時ファイルをクリーンアップ
rm -f "$temp_env" "$temp_example"
echo ""
log_info "=== 差分詳細表示完了 ==="
log_info "注意: 上記の推奨値への変更を検討してください。"
log_info "現在の実装では .env の値を保持します。"
echo ""
}
# 値の変更分析
analyze_value_change() {
local current_value="$1"
local recommended_value="$2"
# 値の特徴を分析
local analysis=""
# 空値チェック
if [[ -z "$current_value" && -n "$recommended_value" ]]; then
analysis=" ${RED}→ 空値から推奨値への設定${NC}"
elif [[ -n "$current_value" && -z "$recommended_value" ]]; then
analysis=" ${RED}→ 推奨値が空値に変更${NC}"
# 数値チェック
elif [[ "$current_value" =~ ^[0-9]+$ && "$recommended_value" =~ ^[0-9]+$ ]]; then
if [[ $current_value -lt $recommended_value ]]; then
analysis=" ${BLUE}→ 数値増加 (${current_value} < ${recommended_value})${NC}"
elif [[ $current_value -gt $recommended_value ]]; then
analysis=" ${YELLOW}→ 数値減少 (${current_value} > ${recommended_value})${NC}"
fi
# ブール値チェック
elif [[ "$current_value" =~ ^(true|false)$ && "$recommended_value" =~ ^(true|false)$ ]]; then
if [[ "$current_value" != "$recommended_value" ]]; then
analysis=" ${BLUE}→ ブール値変更 (${current_value} → ${recommended_value})${NC}"
fi
# URL/エンドポイントチェック
elif [[ "$current_value" =~ ^https?:// || "$recommended_value" =~ ^https?:// ]]; then
analysis=" ${BLUE}→ URL/エンドポイント変更${NC}"
# ファイルパスチェック
elif [[ "$current_value" =~ ^/ || "$recommended_value" =~ ^/ ]]; then
analysis=" ${BLUE}→ ファイルパス変更${NC}"
else
# 長さの比較
local current_len=${#current_value}
local recommended_len=${#recommended_value}
if [[ $current_len -ne $recommended_len ]]; then
analysis=" ${YELLOW}→ 文字列長変更 (${current_len} → ${recommended_len} 文字)${NC}"
fi
fi
if [[ -n "$analysis" ]]; then
echo -e "$analysis"
fi
}
# .envファイルの部分同期
sync_env_file() {
log_info ".env ファイルの部分同期を開始..."
local new_env_file=".env.new"
local preserved_count=0
local updated_count=0
# 新しい.envファイルを作成
> "$new_env_file"
# .env.exampleを基準に処理
while read -r line; do
# コメント行と空行はそのまま保持
if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
echo "$line" >> "$new_env_file"
continue
fi
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
local example_value=$(echo "$parsed" | cut -d'|' -f2-)
local final_value="$example_value"
# 差分ファイルから.envの値があるかチェック
if [[ -f "$DIFF_FILE" ]]; then
local env_value=$(grep "^${key}|" "$DIFF_FILE" 2>/dev/null | cut -d'|' -f2- || echo "")
if [[ -n "$env_value" ]]; then
final_value="$env_value"
log_info " 保持: $key (.env値)"
((preserved_count++))
else
log_info " 更新: $key (.env.example値)"
((updated_count++))
fi
else
log_info " デフォルト: $key (.env.example値)"
((updated_count++))
fi
# 最終的な行を出力
echo "${key}=${final_value}" >> "$new_env_file"
else
# パースできなかった行はそのまま保持
echo "$line" >> "$new_env_file"
fi
done < .env.example
# 新しいファイルを既存ファイルと置き換え
mv "$new_env_file" ".env"
# 差分ファイルをクリーンアップ
rm -f "$DIFF_FILE"
log_success ".env ファイルの部分同期が完了しました"
log_info " 保持された.env値: $preserved_count"
log_info " .env.example値に更新: $updated_count"
}
# 削除された環境変数を検出
detect_removed_variables() {
log_info "削除された環境変数を検出中..."
if [[ ! -f ".env" ]]; then
return
fi
# 一時ファイルを使用してキーを管理
local temp_example="/tmp/example_keys_$$"
local temp_current="/tmp/current_keys_$$"
# .env.exampleのキーを取得
> "$temp_example"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
echo "$key" >> "$temp_example"
fi
done < .env.example
# 既存の.envのキーを取得
> "$temp_current"
while read -r line; do
local parsed=$(parse_env_line "$line")
if [[ $? -eq 0 ]]; then
local key=$(echo "$parsed" | cut -d'|' -f1)
echo "$key" >> "$temp_current"
fi
done < .env
# 削除された変数を検出
local removed_vars=()
while read -r key; do
if ! grep -q "^${key}$" "$temp_example" 2>/dev/null; then
removed_vars+=("$key")
fi
done < "$temp_current"
# 一時ファイルをクリーンアップ
rm -f "$temp_example" "$temp_current"
if [[ ${#removed_vars[@]} -gt 0 ]]; then
log_warning "以下の環境変数が .env.example から削除されています:"
for var in "${removed_vars[@]}"; do
log_warning " - $var"
done
log_warning "これらの変数を .env から手動で削除することを検討してください"
else
log_success "削除された環境変数はありません"
fi
}
# 統計情報表示
show_statistics() {
log_info "同期結果の統計:"
local total_example=$(grep -c "^[^#]*=" .env.example 2>/dev/null || echo "0")
local total_env=$(grep -c "^[^#]*=" .env 2>/dev/null || echo "0")
log_info " .env.example の環境変数: $total_example"
log_info " .env の環境変数: $total_env"
}
# メイン実行関数
main() {
log_info "=== Dify環境変数部分同期スクリプト ==="
log_info "実行開始: $(date)"
# 前提条件チェック
check_files
# バックアップ作成
create_backup
# 差分検出
detect_differences
# 環境ファイル部分同期
sync_env_file
# 削除された変数の検出
detect_removed_variables
# 統計情報表示
show_statistics
log_success "=== 部分同期処理が正常に完了しました ==="
log_info "終了時刻: $(date)"
}
# スクリプトが直接実行された場合のみmain関数を実行
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
主要な特徴
このスクリプトには、以下のような特徴があります。
-
部分同期
- 既存の
.envに設定されている値は原則保持 -
.env.exampleに新しく追加された変数のみ自動追加
- 既存の
-
詳細な差分表示
- 単なる「違い」ではなく、数値・ブール値・URL などの変更内容を分析して表示
-
自動バックアップ
- 実行前に必ず
.envを日時付きでバックアップ
- 実行前に必ず
-
安全性重視
- 本番環境の設定を意図せず上書きしない設計
「全部自動で書き換える」のではなく、
判断が必要な部分は人が確認しやすい形で残す という方針で作っています。
動作イメージ

処理の流れはシンプルです。
-
.env.exampleと.envを読み込む - 環境変数ごとの差分を検出・分析
- 実行前に
.envをバックアップ - 既存の設定は保持しつつ、新規変数のみを
.envに追加 - 更新後の
.envを生成
「安全に差分を取り込みたい」という運用ニーズを、そのままスクリプトに落とし込んだ形です。
背景と課題
Dify の環境変数管理の複雑さ
Dify は非常に柔軟で高機能な OSS である一方、環境変数の数もかなり多くなっています。
データベース、Redis、ストレージ、外部 API、ワーカー設定、セキュリティ関連など、ほぼすべてが環境変数で制御されています。
一部を抜粋すると、次のような項目です。
DB_TYPE=postgresql
REDIS_HOST=redis
STORAGE_TYPE=opendal
UPLOAD_FILE_SIZE_LIMIT=15
VECTOR_STORE=weaviate
これらが 1000 行以上並ぶため、差分を人間が正確に把握するのは現実的ではありません。
手動同期の問題点
これまで手動で同期していた際、特に問題になっていたのは次の点です。
-
作業時間がかかる
差分確認だけで 1〜2 時間かかることも珍しくありません。 -
ヒューマンエラーが起きやすい
新規変数の見落としや、不要な上書きが発生しがちです。 -
本番設定との衝突
開発用デフォルト値が、本番用の調整済み設定を壊してしまうリスクがあります。 -
変更履歴が追いづらい
どの変数が「今回のバージョンで変わったのか」が分かりにくい状態でした。
このままでは、バージョンアップのたびに心理的な負担が大きくなってしまいます。
実装
ファイル構成
スクリプトは Dify の docker ディレクトリ配下に配置する想定です。
./dify/docker/
├── .env.example
├── .env
├── dify-env-sync.sh
├── env-backup/
│ └── .env.backup_YYYYMMDD_HHMMSS
└── docker-compose.yaml
.env.example と .env が同じディレクトリにある前提にすることで、
余計な設定なしに実行できるようにしています。
環境変数の解析
スクリプトでは、.env ファイルを 1 行ずつ解析し、
KEY=VALUE 形式のものだけを対象に処理します。
parse_env_line() {
local line="$1"
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && return 1
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
echo "$key|$value"
return 0
fi
return 1
}
コメント行や空行は無視し、
「環境変数として意味のある行」だけを安全に扱うようにしています。
差分検出と分析
単純な文字列比較だけでなく、
値の種類に応じた差分分析 を行っているのがポイントです。
analyze_value_change() {
if [[ "$current_value" =~ ^[0-9]+$ && "$recommended_value" =~ ^[0-9]+$ ]]; then
echo "→ 数値変更 ($current_value → $recommended_value)"
fi
}
これにより、
- 数値が増えた/減った
- true / false が切り替わった
- URL が変更された
といった情報を、ログ上で直感的に把握できます。
使用方法
スクリプトの実行
cd dify/docker
chmod +x dify-env-sync.sh
./dify-env-sync.sh
特別なオプションは不要で、
そのまま実行するだけで動作します。
実行ログの例
実行時には、以下のようなログが出力されます。
- バックアップ作成
- 差分検出結果
- 保持された設定 / 追加された設定
これにより、「何が起きたのか」を後から追いやすくなっています。
スクリプトの特徴
差分の可視化
検出された差分は、単なる一覧ではなく「意味のある変更」として表示されます。
| 種類 | 例 |
|---|---|
| 数値変更 | UPLOAD_FILE_SIZE_LIMIT: 50 → 15 |
| ブール値変更 | DEBUG: true → false |
| URL変更 | API_URL: localhost → api.example.com |
これにより、「今回はどこを重点的に確認すべきか」が一目で分かります。
カスタム設定の保護
本番環境用に調整した設定は、スクリプト実行後も保持されます。
UPLOAD_FILE_SIZE_LIMIT=50
DB_HOST=prod-db-server
.env.example 側のデフォルト値がどうなっていても、
意図しない上書きは発生しません。
新規変数の自動追加
一方で、新しいバージョンで追加された変数は自動で .env に追加されます。
WORKFLOW_MAX_EXECUTION_STEPS=500
MARKETPLACE_ENABLED=true
これにより、「知らないうちに必要な設定が足りない」という事態を防げます。
実運用での使いどころ
実際の運用では、次のような流れで使っています。
-
.env.exampleの差分を確認 - スクリプトで同期
- DB・セキュリティ関連の設定を重点チェック
- Docker を再起動
ステージング環境で一度実行してから本番に適用することで、
かなり安心感を持ってバージョンアップできるようになりました。
まとめ
Dify のバージョンアップにおける環境変数管理は、
放置すると確実に運用コストが積み上がっていくポイントです。
今回紹介したスクリプトを使うことで、
- 手動作業の大幅削減
- 本番設定の安全な保護
- 差分の見える化
を同時に実現できます。
「毎回 .env.example をにらみながら祈るように更新している」
そんな状況から抜け出したい方の参考になれば幸いです。
参考リンク
- Dify公式リポジトリ:https://github.com/langgenius/dify
Discussion