[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"post-coolify-r2-yedek-geri-yukleme-testi":3},{"data":4,"meta":849},[5],{"id":6,"documentId":7,"title":8,"slug":9,"description":10,"content":11,"date":785,"admin":786,"createdAt":787,"updatedAt":788,"publishedAt":789,"mediumUrl":790,"tags":791,"featured":32,"seoTitle":792,"seoDescription":793,"category":794,"image":795},18,"d1dagpnt4p3r58sjwmpflme8","Yedek Almak Yetmez: Coolify ve R2 Yedeklerimi Geri Yükleyerek Test Ettim","coolify-r2-yedek-geri-yukleme-testi","Coolify ve Cloudflare R2’deki Directus yedeklerimi ayrı bir ortamda geri yükledim. PostgreSQL, dosya bütünlüğü ve panel kontrollerini kullandığım komutlarla anlattım.",[12,18,22,26,29,34,37,41,44,50,53,57,60,64,67,71,75,78,82,85,89,92,96,99,103,106,110,113,119,122,126,129,133,136,140,143,147,150,154,157,161,164,168,171,175,178,182,185,189,192,196,199,203,206,210,213,225,228,232,235,239,242,246,249,253,256,260,263,267,270,274,277,281,284,288,291,295,298,306,309,313,316,320,323,327,330,334,337,341,344,348,351,355,358,362,365,369,373,376,380,383,387,390,394,397,401,404,408,411,415,418,422,425,429,432,436,439,443,446,450,453,457,461,464,472,475,479,482,486,489,497,500,504,507,511,514,522,525,529,532,536,539,543,546,550,553,557,560,564,567,571,574,578,581,585,588,596,599,603,606,610,613,617,620,624,627,631,634,638,641,645,648,652,655,659,662,666,669,677,680,684,687,691,694,698,701,705,708,712,715,719,722,726,729,750,753,757,760,764,767,771,774,778,781],{"type":13,"children":14},"paragraph",[15],{"text":16,"type":17},"Web sitemi Vercel’den Hetzner’e taşıdıktan sonra, sunucu tarafında ilgilenmem gereken işler de arttı. Bunlardan biri yedeklemeydi.","text",{"type":13,"children":19},[20],{"text":21,"type":17},"",{"type":13,"children":23},[24],{"text":25,"type":17},"Coolify üzerinden yedekleri ayarladım, Cloudflare R2’ye gönderildiklerini gördüm. İlk bakışta her şey tamam gibi görünüyordu. Ama aklımda bir soru vardı:",{"type":13,"children":27},[28],{"text":21,"type":17},{"type":13,"children":30},[31],{"bold":32,"text":33,"type":17},true,"Bir sorun yaşarsam bu yedeklerle gerçekten geri dönebilecek miyim?",{"type":13,"children":35},[36],{"text":21,"type":17},{"type":13,"children":38},[39],{"text":40,"type":17},"Bunu öğrenmek için bir sorun çıkmasını beklemek yerine, yedekleri ayrı bir test ortamında geri yükledim. Bu yazıda hem süreci hem de kontrollerde kullandığım komutları paylaşıyorum.",{"type":13,"children":42},[43],{"text":21,"type":17},{"type":45,"level":46,"children":47},"heading",2,[48],{"text":49,"type":17},"Veritabanı yedeği tek başına yeterli mi?",{"type":13,"children":51},[52],{"text":21,"type":17},{"type":13,"children":54},[55],{"text":56,"type":17},"Studioae’de içerik yönetimi için Directus, veritabanı olarak PostgreSQL kullanıyorum. Yazılar ve içerik kayıtları veritabanında, yüklediğim görseller ise ayrı bir Docker volume’ünde tutuluyor.",{"type":13,"children":58},[59],{"text":21,"type":17},{"type":13,"children":61},[62],{"text":63,"type":17},"Bu yüzden iki parçayı birlikte kontrol etmem gerekiyordu:",{"type":13,"children":65},[66],{"text":21,"type":17},{"type":13,"children":68},[69],{"bold":32,"text":70,"type":17},"- PostgreSQL veritabanı yedeği.",{"type":13,"children":72},[73],{"bold":32,"text":74,"type":17},"- Directus’a yüklenen dosyaların yedeği.",{"type":13,"children":76},[77],{"text":21,"type":17},{"type":13,"children":79},[80],{"text":81,"type":17},"Veritabanında bir görselin kaydı bulunması, görsel dosyasının da yedeklendiği anlamına gelmiyor.",{"type":13,"children":83},[84],{"text":21,"type":17},{"type":13,"children":86},[87],{"text":88,"type":17},"Aşağıdaki komutlarda gerçek sunucu adresi ve kaynak adları yerine örnek değerler kullandım. Tablo adları ve dosya yolları kendi kurulumunuza göre değişebilir.",{"type":13,"children":90},[91],{"text":21,"type":17},{"type":13,"children":93},[94],{"bold":32,"text":95,"type":17},"SSH tüneli ve Mac’teki hash kontrolü dışındaki komutları sunucuda çalıştırdım.",{"type":13,"children":97},[98],{"text":21,"type":17},{"type":45,"level":46,"children":100},[101],{"text":102,"type":17},"1. Test için kaynakları belirledim",{"type":13,"children":104},[105],{"text":21,"type":17},{"type":13,"children":107},[108],{"text":109,"type":17},"Önce çalışan container’ları ve Directus’un bağlı olduğu volume’leri listeledim:",{"type":13,"children":111},[112],{"text":21,"type":17},{"type":114,"children":115,"language":118},"code",[116],{"text":117,"type":17},"docker ps --format 'table {{.Names}}\\t{{.Image}}'\n\ndocker inspect DIRECTUS_CONTAINER_ADI \\\n  --format '{{range .Mounts}}{{println .Source \"->\" .Destination}}{{end}}'","plaintext",{"type":13,"children":120},[121],{"text":21,"type":17},{"type":13,"children":123},[124],{"text":125,"type":17},"Ardından aynı terminal oturumunda kullanacağım değişkenleri tanımladım:",{"type":13,"children":127},[128],{"text":21,"type":17},{"type":114,"children":130,"language":118},[131],{"text":132,"type":17},"PG_CONTAINER=\"POSTGRES_CONTAINER_ADI\"\nDIRECTUS_CONTAINER=\"DIRECTUS_CONTAINER_ADI\"\n\nDB_USER=\"directus\"\nTEST_DB=\"directus_restore_test_20261009\"\n\nDB_BACKUP=\"\u002FYEDEK_KLASORU\u002Fveritabani.dmp\"\nUPLOADS_BACKUP=\"\u002FYEDEK_KLASORU\u002Fuploads.tar.gz\"\n\nTEST_DIR=\"\u002Froot\u002Fstudioae-restore-test-20261009\"\nTEST_CONTAINER=\"studioae-directus-restore-test\"\nDOCKER_NETWORK=\"coolify\"",{"type":13,"children":134},[135],{"text":21,"type":17},{"type":13,"children":137},[138],{"text":139,"type":17},"Buradaki büyük harfli örnek değerlerin kendi ortamınızdaki karşılıklarıyla değiştirilmesi gerekiyor. Test veritabanının adını da canlı veritabanından açıkça farklı seçtim.",{"type":13,"children":141},[142],{"text":21,"type":17},{"type":45,"level":46,"children":144},[145],{"text":146,"type":17},"2. Veritabanı yedeğinin içeriğine baktım",{"type":13,"children":148},[149],{"text":21,"type":17},{"type":13,"children":151},[152],{"text":153,"type":17},"Elimdeki yedek, PostgreSQL’in custom arşiv biçimindeydi. İçeriğini `pg_restore` ile listeledim:",{"type":13,"children":155},[156],{"text":21,"type":17},{"type":114,"children":158,"language":118},[159],{"text":160,"type":17},"docker exec -i \"$PG_CONTAINER\" pg_restore --list \\\n  \u003C \"$DB_BACKUP\" | head -n 25",{"type":13,"children":162},[163],{"text":21,"type":17},{"type":13,"children":165},[166],{"text":167,"type":17},"Çıktıda yedeğin oluşturulma zamanı, PostgreSQL sürümü ve tablo kayıtları görünüyordu.",{"type":13,"children":169},[170],{"text":21,"type":17},{"type":13,"children":172},[173],{"text":174,"type":17},"Bu kontrol arşivin okunabildiğini gösterdi. Tam olarak geri yüklenebildiğini anlamak için bir sonraki adıma geçtim.",{"type":13,"children":176},[177],{"text":21,"type":17},{"type":45,"level":46,"children":179},[180],{"text":181,"type":17},"3. Ayrı bir test veritabanına geri yükledim",{"type":13,"children":183},[184],{"text":21,"type":17},{"type":13,"children":186},[187],{"text":188,"type":17},"Önce boş bir test veritabanı oluşturdum:",{"type":13,"children":190},[191],{"text":21,"type":17},{"type":114,"children":193,"language":118},[194],{"text":195,"type":17},"docker exec \"$PG_CONTAINER\" \\\n  createdb -U \"$DB_USER\" -T template0 \"$TEST_DB\"",{"type":13,"children":197},[198],{"text":21,"type":17},{"type":13,"children":200},[201],{"text":202,"type":17},"Sonra yedeği bu veritabanına geri yükledim:",{"type":13,"children":204},[205],{"text":21,"type":17},{"type":114,"children":207,"language":118},[208],{"text":209,"type":17},"docker exec -i \"$PG_CONTAINER\" \\\n  pg_restore \\\n    -U \"$DB_USER\" \\\n    -d \"$TEST_DB\" \\\n    --no-owner \\\n    --no-privileges \\\n    --exit-on-error \\\n    --single-transaction \\\n  \u003C \"$DB_BACKUP\" &&\necho \"Geri yükleme başarılı\"",{"type":13,"children":211},[212],{"text":21,"type":17},{"type":13,"children":214},[215,217,219,221,223],{"text":216,"type":17},"Bu testte sahiplik ve yetki kayıtlarını aktarmadım. `",{"code":32,"text":218,"type":17},"--exit-on-error",{"text":220,"type":17},"` ile hata durumunda işlemin durmasını, `",{"code":32,"text":222,"type":17},"--single-transaction",{"text":224,"type":17},"` ile geri yüklemenin tek işlem olarak yürütülmesini sağladım.",{"type":13,"children":226},[227],{"text":21,"type":17},{"type":13,"children":229},[230],{"text":231,"type":17},"Terminalde beklediğim mesajı gördüm:",{"type":13,"children":233},[234],{"text":21,"type":17},{"type":114,"children":236,"language":118},[237],{"text":238,"type":17},"Geri yükleme başarılı",{"type":13,"children":240},[241],{"text":21,"type":17},{"type":45,"level":46,"children":243},[244],{"text":245,"type":17},"4. İçerik kayıtlarını kontrol ettim",{"type":13,"children":247},[248],{"text":21,"type":17},{"type":13,"children":250},[251],{"text":252,"type":17},"Geri yüklenen veritabanındaki birkaç tablonun kayıt sayısına baktım:",{"type":13,"children":254},[255],{"text":21,"type":17},{"type":114,"children":257,"language":118},[258],{"text":259,"type":17},"docker exec \"$PG_CONTAINER\" \\\n  psql -U \"$DB_USER\" -d \"$TEST_DB\" -c \"\n    SELECT 'About' AS tablo, count(*) AS kayit\n    FROM public.\\\"About\\\"\n    UNION ALL\n    SELECT 'Blog', count(*) FROM public.\\\"Blog\\\"\n    UNION ALL\n    SELECT 'Blog_Category', count(*)\n    FROM public.\\\"Blog_Category\\\"\n    UNION ALL\n    SELECT 'directus_files', count(*)\n    FROM public.directus_files;\n  \"",{"type":13,"children":261},[262],{"text":21,"type":17},{"type":13,"children":264},[265],{"text":266,"type":17},"Sonuçta bir About kaydı, üç blog yazısı, iki blog kategorisi ve 26 dosya kaydı vardı.",{"type":13,"children":268},[269],{"text":21,"type":17},{"type":13,"children":271},[272],{"text":273,"type":17},"Bu sayılar, yedekten gelen kayıtları gösteriyordu. Uygulamada doğru görüntülenip görüntülenmediklerini daha sonra panelden kontrol ettim.",{"type":13,"children":275},[276],{"text":21,"type":17},{"type":45,"level":46,"children":278},[279],{"text":280,"type":17},"5. Dosya yedeğini açıp veritabanıyla eşleştirdim",{"type":13,"children":282},[283],{"text":21,"type":17},{"type":13,"children":285},[286],{"text":287,"type":17},"Önce dosyaların hangi depolamada tutulduğuna baktım:",{"type":13,"children":289},[290],{"text":21,"type":17},{"type":114,"children":292,"language":118},[293],{"text":294,"type":17},"docker exec \"$PG_CONTAINER\" \\\n  psql -U \"$DB_USER\" -d \"$TEST_DB\" -c \"\n    SELECT storage, count(*) AS dosya_sayisi\n    FROM public.directus_files\n    GROUP BY storage;\n  \"",{"type":13,"children":296},[297],{"text":21,"type":17},{"type":13,"children":299},[300,302,304],{"text":301,"type":17},"26 dosyanın tamamı `",{"code":32,"text":303,"type":17},"local",{"text":305,"type":17},"` depolama kullanıyordu.",{"type":13,"children":307},[308],{"text":21,"type":17},{"type":13,"children":310},[311],{"text":312,"type":17},"Arşivin gzip bütünlüğünü kontrol edip içeriğine baktım:",{"type":13,"children":314},[315],{"text":21,"type":17},{"type":114,"children":317,"language":118},[318],{"text":319,"type":17},"gzip -t \"$UPLOADS_BACKUP\" &&\necho \"Arşiv sağlam\"\n\ntar -tzf \"$UPLOADS_BACKUP\" | head -n 15",{"type":13,"children":321},[322],{"text":21,"type":17},{"type":13,"children":324},[325],{"text":326,"type":17},"Sonra kendi oluşturduğum yedek arşivini ayrı bir test klasörüne açtım:",{"type":13,"children":328},[329],{"text":21,"type":17},{"type":114,"children":331,"language":118},[332],{"text":333,"type":17},"mkdir -p \"$TEST_DIR\u002Fuploads\"\n\ntar -xzf \"$UPLOADS_BACKUP\" \\\n  -C \"$TEST_DIR\u002Fuploads\" \\\n  --no-same-owner",{"type":13,"children":335},[336],{"text":21,"type":17},{"type":13,"children":338},[339],{"text":340,"type":17},"Veritabanındaki fiziksel dosya adlarını bir listeye aktardım:",{"type":13,"children":342},[343],{"text":21,"type":17},{"type":114,"children":345,"language":118},[346],{"text":347,"type":17},"docker exec \"$PG_CONTAINER\" \\\n  psql -U \"$DB_USER\" -d \"$TEST_DB\" -At -c \"\n    SELECT filename_disk\n    FROM public.directus_files\n    WHERE storage = 'local';\n  \" > \"$TEST_DIR\u002Ffiles.txt\"",{"type":13,"children":349},[350],{"text":21,"type":17},{"type":13,"children":352},[353],{"text":354,"type":17},"Ardından listedeki her dosyanın test klasöründe bulunup bulunmadığını kontrol ettim:",{"type":13,"children":356},[357],{"text":21,"type":17},{"type":114,"children":359,"language":118},[360],{"text":361,"type":17},"(\n  bulunan=0\n  eksik=0\n\n  while IFS= read -r dosya; do\n    if [ -f \"$TEST_DIR\u002Fuploads\u002F$dosya\" ]; then\n      bulunan=$((bulunan + 1))\n    else\n      printf 'Eksik: %s\\n' \"$dosya\"\n      eksik=$((eksik + 1))\n    fi\n  done \u003C \"$TEST_DIR\u002Ffiles.txt\"\n\n  printf 'Bulunan: %s\\nEksik: %s\\n' \"$bulunan\" \"$eksik\"\n)",{"type":13,"children":363},[364],{"text":21,"type":17},{"type":13,"children":366},[367],{"text":368,"type":17},"Sonuç:",{"type":114,"children":370,"language":118},[371],{"text":372,"type":17},"Bulunan: 26\nEksik: 0",{"type":13,"children":374},[375],{"text":21,"type":17},{"type":13,"children":377},[378],{"text":379,"type":17},"Bu kontrol, veritabanının referans verdiği 26 yerel dosyanın da yedekte bulunduğunu gösterdi. Dosyaların görüntülenmesini ayrıca Directus üzerinden test ettim.",{"type":13,"children":381},[382],{"text":21,"type":17},{"type":45,"level":46,"children":384},[385],{"text":386,"type":17},"6. R2’deki kopyaları karşılaştırdım",{"type":13,"children":388},[389],{"text":21,"type":17},{"type":13,"children":391},[392],{"text":393,"type":17},"Sunucudaki yedekleri test ettikten sonra Cloudflare R2’deki kopyalarını bilgisayarıma indirdim.",{"type":13,"children":395},[396],{"text":21,"type":17},{"type":13,"children":398},[399],{"text":400,"type":17},"Sunucuda:",{"type":13,"children":402},[403],{"text":21,"type":17},{"type":114,"children":405,"language":118},[406],{"text":407,"type":17},"sha256sum \"$DB_BACKUP\"\nsha256sum \"$UPLOADS_BACKUP\"",{"type":13,"children":409},[410],{"text":21,"type":17},{"type":13,"children":412},[413],{"text":414,"type":17},"Mac bilgisayarımda:",{"type":13,"children":416},[417],{"text":21,"type":17},{"type":114,"children":419,"language":118},[420],{"text":421,"type":17},"shasum -a 256 \"$HOME\u002FDownloads\u002FVERITABANI_YEDEGI.dmp\"\n\nshasum -a 256 \"$HOME\u002FDownloads\u002FDOSYA_YEDEGI.tar.gz\"",{"type":13,"children":423},[424],{"text":21,"type":17},{"type":13,"children":426},[427],{"text":428,"type":17},"İki yedeğin de sunucudaki ve bilgisayarımdaki SHA-256 değerleri eşleşti.",{"type":13,"children":430},[431],{"text":21,"type":17},{"type":13,"children":433},[434],{"text":435,"type":17},"Böylece R2’den indirdiğim dosyaların, sunucuda test ettiğim yedeklerle birebir aynı olduğunu doğruladım.",{"type":13,"children":437},[438],{"text":21,"type":17},{"type":45,"level":46,"children":440},[441],{"text":442,"type":17},"7. Geri yüklenen verilerle Directus’u başlattım",{"type":13,"children":444},[445],{"text":21,"type":17},{"type":13,"children":447},[448],{"text":449,"type":17},"Sırada içerikleri uygulama üzerinden görmek vardı.",{"type":13,"children":451},[452],{"text":21,"type":17},{"type":13,"children":454},[455],{"text":456,"type":17},"Canlı sistemde kullanılan Docker image kimliğini aldım:",{"type":114,"children":458,"language":118},[459],{"text":460,"type":17},"DIRECTUS_IMAGE=$(docker inspect \"$DIRECTUS_CONTAINER\" \\\n  --format '{{.Image}}')",{"type":13,"children":462},[463],{"text":21,"type":17},{"type":13,"children":465},[466,468,470],{"text":467,"type":17},"Test ayar dosyasını oluştururken veritabanı şifresini terminale yazdırmadan mevcut container’dan okudum. Dosyaya yalnızca sahibinin erişebilmesi için `",{"code":32,"text":469,"type":17},"600",{"text":471,"type":17},"` izni verdim.",{"type":13,"children":473},[474],{"text":21,"type":17},{"type":13,"children":476},[477],{"text":478,"type":17},"Bu Python komutu, yukarıdaki değişkenlerle aynı sunucu terminalinde çalıştırılabilir:",{"type":13,"children":480},[481],{"text":21,"type":17},{"type":114,"children":483,"language":118},[484],{"text":485,"type":17},"export DIRECTUS_CONTAINER PG_CONTAINER DB_USER TEST_DB TEST_DIR\n\npython3 - \u003C\u003C'PY'\nimport json\nimport os\nimport secrets\nimport subprocess\n\ninfo = json.loads(subprocess.check_output([\n    \"docker\", \"inspect\", os.environ[\"DIRECTUS_CONTAINER\"]\n]))[0]\n\nenv = dict(\n    item.split(\"=\", 1)\n    for item in info[\"Config\"][\"Env\"]\n    if \"=\" in item\n)\n\npassword = env.get(\"DB_PASSWORD\")\nif not password:\n    raise SystemExit(\"DB_PASSWORD bulunamadı.\")\n\nsettings = {\n    \"DB_CLIENT\": \"pg\",\n    \"DB_HOST\": os.environ[\"PG_CONTAINER\"],\n    \"DB_PORT\": \"5432\",\n    \"DB_USER\": os.environ[\"DB_USER\"],\n    \"DB_PASSWORD\": password,\n    \"DB_DATABASE\": os.environ[\"TEST_DB\"],\n    \"SECRET\": secrets.token_hex(32),\n    \"PUBLIC_URL\": \"http:\u002F\u002Flocalhost:18055\",\n    \"STORAGE_LOCATIONS\": \"local\",\n    \"STORAGE_LOCAL_DRIVER\": \"local\",\n    \"STORAGE_LOCAL_ROOT\": \"\u002Fdirectus\u002Fuploads\",\n}\n\nfor key, value in settings.items():\n    if \"\\n\" in value or \"\\r\" in value:\n        raise SystemExit(f\"{key} değeri env dosyası için uygun değil.\")\n\npath = os.path.join(\n    os.environ[\"TEST_DIR\"], \"directus-test.env\"\n)\n\nfd = os.open(\n    path,\n    os.O_WRONLY | os.O_CREAT | os.O_EXCL,\n    0o600,\n)\n\nwith os.fdopen(fd, \"w\") as file:\n    for key, value in settings.items():\n        file.write(f\"{key}={value}\\n\")\n\nprint(\"Test ayarları hazır. Şifre ekrana yazdırılmadı.\")\nPY",{"type":13,"children":487},[488],{"text":21,"type":17},{"type":13,"children":490},[491,493,495],{"text":492,"type":17},"Bu örnek, benim kurulumum gibi `",{"code":32,"text":494,"type":17},"DB_PASSWORD",{"text":496,"type":17},"` ortam değişkeni kullanan ve PostgreSQL container’ına aynı Docker ağı üzerinden erişebilen bir yapı için hazırlanmıştır.",{"type":13,"children":498},[499],{"text":21,"type":17},{"type":13,"children":501},[502],{"text":503,"type":17},"Test uygulamasını açmadan önce geri yüklenen veritabanındaki Directus Flow’larını devre dışı bırakmak da önemlidir. Böylece test ortamındaki işlemler e-posta veya webhook gibi dış etkiler oluşturmaz. Aşağıdaki komutun hedefi yalnızca test veritabanıdır:",{"type":13,"children":505},[506],{"text":21,"type":17},{"type":114,"children":508,"language":118},[509],{"text":510,"type":17},"docker exec \"$PG_CONTAINER\" \\\n  psql -U \"$DB_USER\" -d \"$TEST_DB\" -c \"\n    UPDATE public.directus_flows\n    SET status = 'inactive'\n    WHERE status = 'active';\n  \"",{"type":13,"children":512},[513],{"text":21,"type":17},{"type":13,"children":515},[516,518,520],{"text":517,"type":17},"Test dosyalarının sahipliğini image içindeki `",{"code":32,"text":519,"type":17},"node",{"text":521,"type":17},"` kullanıcısına ayarladım:",{"type":13,"children":523},[524],{"text":21,"type":17},{"type":114,"children":526,"language":118},[527],{"text":528,"type":17},"docker run --rm --user 0 --entrypoint sh \\\n  -v \"$TEST_DIR\u002Fuploads:\u002Frestore-uploads\" \\\n  \"$DIRECTUS_IMAGE\" \\\n  -c 'chown -R node:node \u002Frestore-uploads'",{"type":13,"children":530},[531],{"text":21,"type":17},{"type":13,"children":533},[534],{"text":535,"type":17},"Ardından test container’ını başlattım:",{"type":13,"children":537},[538],{"text":21,"type":17},{"type":114,"children":540,"language":118},[541],{"text":542,"type":17},"docker run -d \\\n  --name \"$TEST_CONTAINER\" \\\n  --network \"$DOCKER_NETWORK\" \\\n  -p 127.0.0.1:18055:8055 \\\n  --env-file \"$TEST_DIR\u002Fdirectus-test.env\" \\\n  -v \"$TEST_DIR\u002Fuploads:\u002Fdirectus\u002Fuploads\" \\\n  \"$DIRECTUS_IMAGE\"",{"type":13,"children":544},[545],{"text":21,"type":17},{"type":13,"children":547},[548],{"text":549,"type":17},"Bu testte veritabanı ve uploads yedeğini kullandım. Özel extension ve template volume’lerini test container’ına bağlamadım.",{"type":13,"children":551},[552],{"text":21,"type":17},{"type":13,"children":554},[555],{"text":556,"type":17},"Logları ve sağlık durumunu kontrol ettim:",{"type":13,"children":558},[559],{"text":21,"type":17},{"type":114,"children":561,"language":118},[562],{"text":563,"type":17},"docker logs --tail 60 \"$TEST_CONTAINER\"\n\ncurl -sS --max-time 10 \\\n  http:\u002F\u002F127.0.0.1:18055\u002Fserver\u002Fhealth",{"type":13,"children":565},[566],{"text":21,"type":17},{"type":13,"children":568},[569],{"text":570,"type":17},"Sağlık kontrolünün sonucu:",{"type":13,"children":572},[573],{"text":21,"type":17},{"type":114,"children":575,"language":118},[576],{"text":577,"type":17},"{\"status\":\"ok\"}",{"type":13,"children":579},[580],{"text":21,"type":17},{"type":45,"level":46,"children":582},[583],{"text":584,"type":17},"8. SSH tüneliyle test paneline girdim",{"type":13,"children":586},[587],{"text":21,"type":17},{"type":13,"children":589},[590,592,594],{"text":591,"type":17},"Test portunu sunucuda yalnızca `",{"code":32,"text":593,"type":17},"127.0.0.1",{"text":595,"type":17},"` adresine bağlamıştım. Paneli bilgisayarımdan açmak için Mac terminalinde SSH tüneli oluşturdum:",{"type":13,"children":597},[598],{"text":21,"type":17},{"type":114,"children":600,"language":118},[601],{"text":602,"type":17},"ssh \\\n  -i ~\u002F.ssh\u002FSUNUCU_SSH_ANAHTARI \\\n  -o IdentitiesOnly=yes \\\n  -o ExitOnForwardFailure=yes \\\n  -N \\\n  -L 18055:127.0.0.1:18055 \\\n  root@SUNUCU_IP_ADRESI",{"type":13,"children":604},[605],{"text":21,"type":17},{"type":13,"children":607},[608],{"text":609,"type":17},"Tünel açıkken tarayıcıdan şu adrese girdim:",{"type":13,"children":611},[612],{"text":21,"type":17},{"type":114,"children":614,"language":118},[615],{"text":616,"type":17},"http:\u002F\u002Flocalhost:18055\u002Fadmin",{"type":13,"children":618},[619],{"text":21,"type":17},{"type":13,"children":621},[622],{"text":623,"type":17},"Panele giriş yapıp blog yazılarını, içerikleri ve görselleri açtım. Kontrol ettiğim içerikler görüntüleniyordu.",{"type":13,"children":625},[626],{"text":21,"type":17},{"type":13,"children":628},[629],{"text":630,"type":17},"Benim için testin en rahatlatıcı kısmı buydu: Yedek dosyaları, tekrar kullanılabilen bir uygulamaya dönüşmüştü.",{"type":13,"children":632},[633],{"text":21,"type":17},{"type":45,"level":46,"children":635},[636],{"text":637,"type":17},"9. Test ortamını temizledim",{"type":13,"children":639},[640],{"text":21,"type":17},{"type":13,"children":642},[643],{"text":644,"type":17},"Kontroller tamamlanınca yalnızca oluşturduğum test kaynaklarını kaldırdım:",{"type":13,"children":646},[647],{"text":21,"type":17},{"type":114,"children":649,"language":118},[650],{"text":651,"type":17},"docker rm -f \"$TEST_CONTAINER\"\n\ndocker exec \"$PG_CONTAINER\" \\\n  dropdb -U \"$DB_USER\" \"$TEST_DB\"",{"type":13,"children":653},[654],{"text":21,"type":17},{"type":13,"children":656},[657],{"text":658,"type":17},"Geçici klasörü silerken hedefi açıkça kontrol eden bir koşul kullandım:",{"type":13,"children":660},[661],{"text":21,"type":17},{"type":114,"children":663,"language":118},[664],{"text":665,"type":17},"if [ \"$TEST_DIR\" = \"\u002Froot\u002Fstudioae-restore-test-20261009\" ]; then\n  rm -rf -- \"$TEST_DIR\"\nelse\n  printf 'Beklenmeyen klasör; silinmedi: %s\\n' \"$TEST_DIR\"\nfi",{"type":13,"children":667},[668],{"text":21,"type":17},{"type":13,"children":670},[671,673,675],{"text":672,"type":17},"Son olarak Mac’te SSH tünelinin açık olduğu terminalde `",{"code":32,"text":674,"type":17},"Control + C",{"text":676,"type":17},"` ile bağlantıyı kapattım.",{"type":13,"children":678},[679],{"text":21,"type":17},{"type":45,"level":46,"children":681},[682],{"text":683,"type":17},"Bildirim tarafında da küçük bir sorun çıktı",{"type":13,"children":685},[686],{"text":21,"type":17},{"type":13,"children":688},[689],{"text":690,"type":17},"Yedekleme başarısız olursa haberdar olmak için Coolify’de e-posta bildirimlerini ayarladım.",{"type":13,"children":692},[693],{"text":21,"type":17},{"type":13,"children":695},[696],{"text":697,"type":17},"Test maili gelmeyince, sunucudan Gmail’e bağlantıyı kontrol ettim. 465 portu zaman aşımına uğrarken 587 portuna bağlantı kurulabildi. Port ve şifreleme ayarını düzelttikten sonra test maili ulaştı.",{"type":13,"children":699},[700],{"text":21,"type":17},{"type":13,"children":702},[703],{"text":704,"type":17},"Bu adımda e-posta gönderimini doğruladım ve yedekleme hatası bildirimini etkin bıraktım. Gerçek bir yedekleme hatası oluşturarak ayrıca bildirim testi yapmadım.",{"type":13,"children":706},[707],{"text":21,"type":17},{"type":45,"level":46,"children":709},[710],{"text":711,"type":17},"Bu testten ne öğrendim?",{"type":13,"children":713},[714],{"text":21,"type":17},{"type":13,"children":716},[717],{"text":718,"type":17},"Yedeklerin listede görünmesi iyi bir başlangıç. Ancak geri yükleme denemesi yapmak, neyi yedeklediğimi ve nasıl geri dönebileceğimi çok daha net anlamamı sağladı.",{"type":13,"children":720},[721],{"text":21,"type":17},{"type":13,"children":723},[724],{"text":725,"type":17},"Bu çalışmada:",{"type":13,"children":727},[728],{"text":21,"type":17},{"type":730,"format":731,"children":732},"list","unordered",[733,738,742,746],{"type":734,"children":735},"list-item",[736],{"text":737,"type":17},"Veritabanını ayrı bir test veritabanına geri yükledim.",{"type":734,"children":739},[740],{"text":741,"type":17},"Veritabanındaki 26 dosya kaydını arşivle eşleştirdim.",{"type":734,"children":743},[744],{"text":745,"type":17},"R2’den indirdiğim kopyaların SHA-256 değerlerini karşılaştırdım.",{"type":734,"children":747},[748],{"text":749,"type":17},"Directus panelinden içerikleri ve görselleri kontrol ettim.",{"type":13,"children":751},[752],{"text":21,"type":17},{"type":13,"children":754},[755],{"text":756,"type":17},"Bu, bütün sunucuyu sıfırdan kurduğum kapsamlı bir felaket kurtarma provası değildi. Directus’un veritabanı ve yüklenen dosyaları için yaptığım bir geri yükleme testiydi.",{"type":13,"children":758},[759],{"text":21,"type":17},{"type":13,"children":761},[762],{"text":763,"type":17},"Ayrıca veritabanı ve dosya yedekleri farklı saatlerde alındığı için, dosyaların eşleştiğini doğrulamak özellikle değerliydi. Bu testte eksik dosya çıkmadı; aynı sonucun gelecekteki her yedek çifti için geçerli olduğunu varsaymamak gerekiyor.",{"type":13,"children":765},[766],{"text":21,"type":17},{"type":13,"children":768},[769],{"text":770,"type":17},"Başlangıçtaki soruma ise somut bir cevap alabildim:",{"type":13,"children":772},[773],{"text":21,"type":17},{"type":13,"children":775},[776],{"bold":32,"text":777,"type":17},"Test ettiğim yedeklerle içeriklerimi ve dosyalarımı geri getirebildim.",{"type":13,"children":779},[780],{"text":21,"type":17},{"type":13,"children":782},[783],{"text":784,"type":17},"Bundan sonra yedeklemeyi yalnızca “dosya oluştu mu?” diye kontrol etmeyeceğim. Önemli değişikliklerden sonra geri yükleme sürecini de yeniden deneyeceğim.","2026-10-09","Aleyna","2026-10-09T15:52:31.829Z","2026-10-09T16:16:34.032Z","2026-10-09T16:16:34.148Z","https:\u002F\u002Fmedium.com\u002F@aleynaertin1\u002Fyedek-almak-yetmez-coolify-ve-r2-yedeklerimi-geri-y%C3%BCkleyerek-test-ettim-b12d9d277eb3","Coolify, Cloudflare R2, Directus, PostgreSQL, Docker, Yedekleme, Hetzner","Coolify ve R2 Yedeklerini Geri Yükleme Testi","Directus ve PostgreSQL yedeklerini geri yükleme, dosyaları eşleştirme ve R2 kopyalarını SHA-256 ile doğrulama adımlarını komutlarla paylaşıyorum.","Yazılım",{"id":796,"documentId":797,"name":798,"alternativeText":799,"caption":799,"focalPoint":799,"width":800,"height":801,"formats":802,"hash":842,"ext":804,"mime":807,"size":843,"url":844,"previewUrl":799,"provider":845,"provider_metadata":846,"createdAt":847,"updatedAt":848,"publishedAt":847},7,"ay0p6gz44stp2ldbwbqdqk63","coolify-r2-yedek-geri-yukleme-testi.webp",null,1672,941,{"large":803,"small":815,"medium":824,"thumbnail":833},{"ext":804,"url":805,"hash":806,"mime":807,"name":808,"path":799,"size":809,"width":810,"height":811,"sizeInBytes":812,"provider_metadata":813},".webp","https:\u002F\u002Fres.cloudinary.com\u002Fdgbo8yrxg\u002Fimage\u002Fupload\u002Fv1791562573\u002Flarge_coolify_r2_yedek_geri_yukleme_testi_727151d568.webp","large_coolify_r2_yedek_geri_yukleme_testi_727151d568","image\u002Fwebp","large_coolify-r2-yedek-geri-yukleme-testi.webp",26.44,1000,563,26442,{"public_id":806,"resource_type":814},"image",{"ext":804,"url":816,"hash":817,"mime":807,"name":818,"path":799,"size":819,"width":820,"height":821,"sizeInBytes":822,"provider_metadata":823},"https:\u002F\u002Fres.cloudinary.com\u002Fdgbo8yrxg\u002Fimage\u002Fupload\u002Fv1791562573\u002Fsmall_coolify_r2_yedek_geri_yukleme_testi_727151d568.webp","small_coolify_r2_yedek_geri_yukleme_testi_727151d568","small_coolify-r2-yedek-geri-yukleme-testi.webp",10.78,500,281,10776,{"public_id":817,"resource_type":814},{"ext":804,"url":825,"hash":826,"mime":807,"name":827,"path":799,"size":828,"width":829,"height":830,"sizeInBytes":831,"provider_metadata":832},"https:\u002F\u002Fres.cloudinary.com\u002Fdgbo8yrxg\u002Fimage\u002Fupload\u002Fv1791562572\u002Fmedium_coolify_r2_yedek_geri_yukleme_testi_727151d568.webp","medium_coolify_r2_yedek_geri_yukleme_testi_727151d568","medium_coolify-r2-yedek-geri-yukleme-testi.webp",18.19,750,422,18188,{"public_id":826,"resource_type":814},{"ext":804,"url":834,"hash":835,"mime":807,"name":836,"path":799,"size":837,"width":838,"height":839,"sizeInBytes":840,"provider_metadata":841},"https:\u002F\u002Fres.cloudinary.com\u002Fdgbo8yrxg\u002Fimage\u002Fupload\u002Fv1791562572\u002Fthumbnail_coolify_r2_yedek_geri_yukleme_testi_727151d568.webp","thumbnail_coolify_r2_yedek_geri_yukleme_testi_727151d568","thumbnail_coolify-r2-yedek-geri-yukleme-testi.webp",4.19,245,138,4192,{"public_id":835,"resource_type":814},"coolify_r2_yedek_geri_yukleme_testi_727151d568",51.26,"https:\u002F\u002Fres.cloudinary.com\u002Fdgbo8yrxg\u002Fimage\u002Fupload\u002Fv1791562571\u002Fcoolify_r2_yedek_geri_yukleme_testi_727151d568.webp","cloudinary",{"public_id":842,"resource_type":814},"2026-10-09T16:16:13.509Z","2026-10-09T16:16:27.427Z",{"pagination":850},{"page":851,"pageSize":852,"pageCount":851,"total":851},1,25]