#Runbook — yedekleme ve geri yükleme
Denenmemiş yedek, yedek değildir. Bu dosyadaki geri yükleme prosedürü 14 Ağustos 2026'da baştan sona çalıştırıldı: veritabanı tamamen düşürüldü ve yedekten geri getirildi. Sonuç bölüm 4'te.
#1. Yedek nasıl alınıyor
Uygulama her gece 02:00 UTC'de pg_dump --format=custom çalıştırır ve çıktıyı /data/backups altına yazar (notusship-YYYYAAGG-SSDDss.dump). Varsayılan saklama süresi 14 gün; eskiler otomatik silinir.
İş Hangfire ile zamanlanır. Süreç yedek sırasında yeniden başlarsa iş kaybolmaz.
Elle yedek almak için:
docker compose exec app dotnet NotusShip.Api.dll backup-now
Zamanlanmış işle aynı kodu çalıştırır. Yükseltmeden önce her zaman bunu çalıştırın.
#Yedeklerin sunucu dışına çıkarılması
V0 yedeği yerel bir volume'a yazar. Sunucunun tamamı kaybolursa yedek de kaybolur; bu yüzden notusship-backups volume'unu dışarı senkronlamak operatörün işidir:
# örnek: gecelik dış kopyalama (cron)
docker run --rm -v notusship_notusship-backups:/backups:ro -v /mnt/yedek:/hedef \
alpine sh -c 'cp -a /backups/. /hedef/'
S3 uyumlu depoya doğrudan yükleme V1'de gelecek — docs/02-fonksiyonlar.md S3 depolamayı V1 kapsamına koyuyor ve tek bir yedek özelliği için SDK eklemek V0'ı ağırlaştırırdı.
#Dosya ekleri ayrı yedeklenir
pg_dump yalnızca veritabanını alır. Issue ekleri notusship-files volume'undadır ve ayrıca kopyalanmalıdır. Veritabanını geri yükleyip dosyaları geri yüklememek, ekleri kırık bağlantıya çevirir.
#2. Geri yükleme prosedürü
Süre: yaklaşık 2 dakika. Uygulama bu süre boyunca kapalıdır.
#Adım 0 — hangi yedeği kullanacağınızı seçin
docker compose exec db ls -lh /backups
#Adım 1 — uygulamayı durdurun
docker compose stop app
Bu adım atlanamaz. Uygulama açıkken geri yüklerseniz, açılışta çalışan migration tabloları önceden oluşturur ve pg_restore yüzlerce çakışma hatası yutarak yarı dolu bir veritabanı bırakır. Bu prosedür yazılırken tam olarak bu oldu.
#Adım 2 — veritabanını boşaltın
docker compose exec db psql -U notusship -d postgres -c 'DROP DATABASE notusship;'
docker compose exec db psql -U notusship -d postgres -c 'CREATE DATABASE notusship OWNER notusship;'
#Adım 3 — geri yükleyin
docker compose exec db pg_restore -U notusship -d notusship \
--no-owner --no-privileges /backups/notusship-20260814-182013.dump
Komut db konteynerinden çalışır; yedek volume'u oraya salt okunur bağlanmıştır. Uygulama konteynerine ihtiyaç yoktur — zaten kapalıdır.
Çıktı boş olmalı. Hata satırı görürseniz devam etmeyin, bölüm 3'e bakın.
#Adım 4 — uygulamayı başlatın
docker compose start app
docker compose logs app --tail 20
Log'da Database is up to date, no migrations to apply. görmelisiniz. Bunun yerine N migration(s) to apply yazıyorsa yedek sizin sürümünüzden eski demektir; bu normaldir ve migration'lar üstüne uygulanır.
Loglar İngilizce, arayüz Türkçe. Operatöre bakan her metin İngilizce sabit (NSHIP-118): bir hata mesajını arama motorunda aratan kişi sonuç bulabilsin diye.
#Adım 5 — doğrulayın
curl -s localhost:8080/health/ready
Sonra arayüzden giriş yapıp bir issue açın. Okuma çalışıp yazma çalışmıyorsa issue sayacı geri gelmemiş olabilir — bölüm 3.
#2b. Geri yükleme — harici PostgreSQL kullanıyorsanız
db konteyneri yok, dolayısıyla pg_restore'u uygulama konteyneri çalıştırır; yedek dizini zaten orada bağlı.
Sıra biraz farklı: uygulamayı durduramazsınız, çünkü komutu çalıştıracak konteyner o. Bunun yerine --clean --if-exists kullanılır — geri yükleme, açılışta migration'ların oluşturduğu nesneleri düşürüp yerine yedektekileri koyar.
# 1) Yedeği seçin
docker compose exec app ls -lh /data/backups
# 2) Geri yükleyin (uygulama AYAKTA, --clean şart)
docker compose exec app sh -c 'PGPASSWORD=<şifre> pg_restore \
--host <db-adresi> --port 5432 --username <kullanıcı> --dbname <veritabanı> \
--no-owner --no-privileges --clean --if-exists /data/backups/<dosya>.dump'
# 3) Uygulamayı yeniden başlatın — bağlantı havuzu bayat tablolara bakıyor
docker compose restart app
Log'da Database is up to date, no migrations to apply. görmelisiniz.
--clean --if-exists olmadan yüzlerce "already exists" hatası alır ve yarı dolu bir veritabanıyla kalırsınız. Paket kurulumunda bu gerekmiyor çünkü orada uygulamayı durdurup boş veritabanına geri yükleyebiliyoruz (§2).
Denendi: 16 Ağustos 2026, gerçek harici PostgreSQL. Veritabanı DROP edildi, uygulama konteynerinden geri yüklendi, kullanıcı yeniden giriş yaptı, migration geçmişi yerindeydi.
#3. Sık karşılaşılan sorunlar
| Belirti | Sebep | Çözüm |
|---|---|---|
pg_restore: error: could not execute query: ERROR: relation already exists | Uygulama kapatılmadan geri yüklendi | Adım 1'den itibaren baştan yapın |
pg_dump: error: aborting because of server version mismatch | İmajdaki istemci sunucudan eski | İmajı yükseltin; uygulama bunu başlangıçta da log'a yazar |
| Yedek dosyası 0 bayt | pg_dump başarısız oldu | Bu dosyayı kullanmayın; iş yarım dosyayı siler, elle alınanı silmez |
| Giriş çalışıyor ama yeni issue açılamıyor | Yarım geri yükleme | Baştan geri yükleyin |
| Geri yükleme sonrası ekler açılmıyor | notusship-files geri yüklenmedi | Dosya volume'unu da geri yükleyin |
#4. Yapılan tatbikat (14 Ağustos 2026)
Kurulum: temiz docker compose up -d, ilk kullanıcı kaydı, bir proje (GERI), bir issue (GERI-1).
1. backup-now → notusship-20260814-182013.dump (77 KB, 0.07 sn)
2. docker compose stop app → durdu
3. DROP DATABASE notusship → public şemada 0 tablo kaldı
4. pg_restore … → çıkış kodu 0, hata yok
5. docker compose start app → "Database is up to date, no migrations to apply."
Doğrulama:
| Kontrol | Sonuç |
|---|---|
| Giriş | 200 |
| Proje listesi | GERI · Geri Yükleme Testi |
| Issue | GERI-1 · Bu issue geri yüklenmeli · Bug |
| Geri yüklemeden sonra yazma | GERI-2 oluştu — issue sayacı da doğru geri geldi |
Sayacın doğru dönmesi önemliydi: projects.issue_counter geri gelmeseydi yeni issue'lar var olan anahtarlarla çakışırdı ve bu ancak günler sonra fark edilirdi.
#5. Yükseltme
docker compose exec app dotnet NotusShip.Api.dll backup-now # önce yedek
docker compose pull
docker compose up -d
docker compose logs app --tail 30 # migration'ları izleyin
Yükseltmeden sonra kullanıcı beyaz ekran + "Beklenmeyen bir hata oluştu" görüyorsa: tarayıcısında bu sürümden önce alınmış bir index.html var. Bir kez sert yenileme (Cmd/Ctrl + Shift + R) çözer ve tekrarlamaz.
Migration'lar açılışta otomatik uygulanır ve log'a yazılır. Kolon silme/yeniden adlandırma iki aşamada yapıldığı için (CLAUDE.md kural 5) bir sürüm geri dönmek veri kaybetmez — ama yine de önce yedek alın.