~/yazilar/dokploy-github-deploy-veritabani-ve-yedekler

Dokploy'da otomatik deploy, veritabanı ve yedekler

8 dk okuma

Yedek almak panelde bir ayar. Yedekten dönebilmek ise ayrı bir iş ve çoğu zaman ilk kez gerçekten ihtiyaç duyulduğu gün denenmiş oluyor. Son bölümü bu yüzden aldığın yedeği Dokploy olmadan da geri yükleyebildiğini görmeye ayırdım.

Oraya gelene kadar sırayla GitHub bağlantısı ve push ile deploy, ortam değişkenleri, PostgreSQL ve S3 uyumlu depoya giden yedekler var.

Örnek olarak kökünde Dockerfile olan, DATABASE_URL değişkeninden PostgreSQL'e bağlanan ve 8080 portunu dinleyen bir web uygulaması varsayıyorum. Adı duyuru. Dili ve çatısı önemli değil.

GitHub'ı bağlamak

Dokploy GitHub'a bir GitHub App üzerinden bağlanıyor. Kişisel erişim token'ı yerine App kullanmak daha temiz: hangi repolara erişebileceğini sen seçiyorsun, yetkiler repo bazında ve kimsenin kişisel hesabına bağlı değil.

Panelde Settings altındaki Git bölümünden GitHub'ı seçip App oluşturma akışını başlatıyorum. Dokploy seni GitHub'a yönlendiriyor, orada App'in kişisel hesabında mı yoksa bir organizasyonda mı oluşturulacağını seçiyorsun. Kurumsal repolar bir organizasyondaysa App'i oraya kur. Kişisel hesaba kurulan App, o kişi ayrıldığında sorun çıkarır.

App'i kurarken GitHub hangi repolara erişim verileceğini soruyor. "All repositories" yerine "Only select repositories" seçip sadece deploy edilecek repoları işaretliyorum. Dokploy sunucusu ele geçirilirse saldırganın erişebileceği kod bu listeyle sınırlı kalıyor.

Burada kurumlarda sık karşılaşılan bir engel var. Push sonrası otomatik deploy, GitHub'ın Dokploy'a bir webhook isteği göndermesiyle çalışıyor. Yani GitHub'ın panel.example.com adresine internetten ulaşabilmesi gerekiyor. Dokploy sunucun sadece kampüs ağından erişilebiliyorsa repo bağlanır, elle deploy çalışır ama push ile hiçbir şey olmaz. Bu durumda birkaç seçenek var: kurum içinde GitLab ya da Gitea varsa Dokploy onları da destekliyor, ya da kampüs içinde çalışan bir GitHub Actions self-hosted runner deploy'u tetikleyebilir.

Uygulamayı repodan oluşturmak

Projenin içinde yeni bir Application açıp kaynak olarak GitHub'ı seçiyorum, sonra repo ve dalı (main) belirliyorum.

Build tipi olarak Dockerfile seçiyorum. Dokploy, Nixpacks gibi repoya bakıp dili tahmin eden ve kendi build tarifini çıkaran araçları da destekliyor. Hızlı başlamak için fena değil, ama image'ın içine ne girdiğini sen belirlemiyorsun ve aracın bir sonraki sürümü farklı bir şey üretebiliyor. Repoda bir Dockerfile varsa build her yerde aynı şekilde çalışıyor, yerelde de CI'da da. Dockerfile'ın kendisi için küçük ve güvenli image yazısındaki kurallar burada da geçerli.

Dockerfile repo kökünde değilse yolunu ayrıca veriyorsun. Monorepo kullanıyorsan build bağlamını alt dizine çekmek için de ayrı bir alan var.

Uygulamanın genel ayarlarında Autodeploy seçeneğinin açık olduğundan emin olup ilk deploy'u elle başlatıyorum. Deployments sekmesi build loglarını canlı gösteriyor:

Cloning Repo github.com/ornek-org/duyuru to /etc/dokploy/applications/duyuru-x7k2qp/code
Building duyuru-x7k2qp
#1 [internal] load build definition from Dockerfile
...
#12 naming to docker.io/library/duyuru-x7k2qp:latest done
Docker Deployed

Dokploy uygulama adlarına rastgele bir sonek ekliyor (duyuru-x7k2qp gibi). Bu ad aynı zamanda container'ın Docker ağındaki adı. Birazdan veritabanı bağlantısında aynı mantık karşına çıkacak.

Logdaki latest etiketi dikkatini çekebilir. Burada image registry'ye gitmiyor, sunucuda build edilip doğrudan çalıştırılıyor. Hangi kodun çalıştığını commit belirliyor, Deployments sekmesi her deploy'un commit'ini gösteriyor. Image'ı bir registry'den çekiyor olsaydın yine sürüm etiketi kullanmak gerekirdi.

Şimdi main dalına küçük bir değişiklik gönder. Birkaç saniye içinde Deployments sekmesinde yeni bir kayıt görmen gerekiyor. Görmüyorsan GitHub'da App'in ayarlarındaki "Recent Deliveries" bölümüne bak. Webhook'un gidip gitmediği ve Dokploy'un ne cevap verdiği orada yazıyor. Zaman aşımı görüyorsan yukarıdaki erişim sorunu.

Ortam değişkenleri

Uygulamanın Environment sekmesinde değişkenler .env dosyası biçiminde, satır satır yazılıyor:

NODE_ENV=production
APP_BASE_URL=https://duyuru.apps.example.com
SESSION_SECRET=degistir-beni
DATABASE_URL=

DATABASE_URL'i şimdilik boş bıraktım, veritabanını açınca dolduracağım. Değişkenleri kaydetmek çalışan container'ı değiştirmiyor, yeni değerlerin geçmesi için yeniden deploy etmek gerekiyor. Kubernetes yazısındaki ConfigMap ile aynı durum.

Aynı sekmede build sırasında kullanılan argümanlar için ayrı bir alan var. Oraya parola ya da token koyma. Build argümanları image katmanlarına ve build geçmişine yazılıyor, docker history ile okunabiliyor. Gizli değerler sadece çalışma zamanı değişkenlerinde dursun.

Birden fazla uygulama aynı değişkeni kullanıyorsa proje düzeyinde tanımlayıp uygulamada referans verebiliyorsun. Bu özelliğin sözdizimi sürümler arasında değişti, kullanmadan önce kendi sürümünün dokümantasyonuna bak.

Veritabanı

Projeye Create Service ile bir veritabanı ekleyip PostgreSQL'i seçiyorum. Panel ad, veritabanı adı, kullanıcı, parola ve image soruyor:

  • Ad: duyuru-db
  • Veritabanı: duyuru
  • Kullanıcı: duyuru
  • Parola: panelin ürettiği değer ya da parola yöneticisinden üretilmiş uzun bir değer
  • Image: postgres:16

Image'ı burada da bir ana sürüme sabitliyorum. PostgreSQL ana sürüm yükseltmesi veri dizininin yeniden oluşturulmasını istiyor, etiket kendiliğinden bir üst sürüme kayarsa veritabanı açılmıyor.

Oluşturunca veritabanının sayfasında iki bağlantı adresi görüyorsun: iç ve dış. İç adres şuna benziyor:

postgresql://duyuru:<parola>@duyuru-db-a1b2c3:5432/duyuru

Host kısmı yine rastgele sonekli servis adı. Uygulama ve veritabanı aynı Docker ağında olduğu için uygulama bu adla bağlanıyor. Bu adresi uygulamanın DATABASE_URL değişkenine yapıştırıp yeniden deploy ediyorum.

Dış port ayarına dokunmuyorum. Onu doldurursan PostgreSQL sunucunun bir portunda dışarı açılıyor ve UFW bunu yine durdurmuyor. Veritabanına elle bakman gerekiyorsa sunucuya SSH ile girip container'ın içinde psql çalıştırmak yeter:

sudo docker exec -it "$(sudo docker ps -q -f name=duyuru-db)" psql -U duyuru -d duyuru

Yedekler için S3 uyumlu bir hedef

Sunucu, disk ya da Dokploy kurulumu giderse aynı sunucuda duran yedek de onunla gidiyor. Dokploy yedekleri S3 uyumlu bir depoya gönderiyor. Kurumda MinIO, Ceph RGW ya da benzeri bir nesne deposu varsa onu kullanabilirsin, yoksa ticari S3 uyumlu servislerden biri de olur.

Depo tarafında bu iş için ayrı bir bucket ve sadece o bucket'a yetkisi olan ayrı bir erişim anahtarı açıyorum. Yönetici anahtarını Dokploy'a vermek, Dokploy ele geçirildiğinde bütün depoyu vermek demek. MinIO'da mc ile şöyle:

mc mb yedek-depo/dokploy-yedek
mc version enable yedek-depo/dokploy-yedek
mc admin user add yedek-depo dokploy-yedek   # gizli anahtarı soruyor, komut satırına yazmıyorum
mc admin policy create yedek-depo dokploy-yedek-policy dokploy-yedek.json
mc admin policy attach yedek-depo dokploy-yedek-policy --user dokploy-yedek

Politika dosyası:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
      "Resource": ["arn:aws:s3:::dokploy-yedek"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::dokploy-yedek/*"]
    }
  ]
}

DeleteObject yetkisini Dokploy'un eski yedekleri silebilmesi için verdim. Bunun bir bedeli var: anahtarı ele geçiren biri yedekleri de silebiliyor. Bucket'ta sürümlemeyi bu yüzden açtım, silinen nesnenin önceki sürümü depoda kalıyor. Depon destekliyorsa object lock ve eski sürümler için bir yaşam döngüsü kuralı eklemek daha da iyi.

Panelde Settings altındaki S3 hedefleri bölümüne bu bilgileri giriyorum: ad, erişim anahtarı, gizli anahtar, bucket, bölge ve endpoint (https://s3.example.com). Kaydetmeden önce bağlantıyı test eden düğmeye basıyorum. Test başarısızsa en sık sebep endpoint adresi ya da bölge değeri. MinIO'da bölge çoğu zaman us-east-1 olarak bırakılıyor.

Yedek zamanlaması

Veritabanının sayfasında Backups sekmesinden yeni bir yedek tanımı ekliyorum:

  • Hedef: az önce eklediğim S3 hedefi
  • Veritabanı: duyuru
  • Zamanlama: 0 3 * * * (her gece)
  • Önek: duyuru-db/
  • Saklanacak yedek sayısı: 14

Zamanlamanın hangi saat dilimine göre çalıştığını kontrol et. Container'lar çoğu zaman UTC ile çalışıyor, gece 3 sandığın şey yerel saatle sabah 6 olabilir.

Tanımı kaydettikten sonra geceyi beklemiyorum, elle bir kez çalıştırıyorum. Sonra yedeğin gerçekten depoya gidip gitmediğine paneli kullanmadan bakıyorum. Bunun için yönetim makinesinde AWS CLI'a ayrı bir profil tanımlamak işe yarıyor (aws configure --profile yedek). Bu profilde Dokploy'un anahtarını değil, aynı bucket'ı sadece okuyabilen ikinci bir anahtarı kullanmak daha doğru:

aws --profile yedek --endpoint-url https://s3.example.com \
  s3 ls s3://dokploy-yedek/duyuru-db/
2026-10-18 03:00:14     184233 2026-10-18T03-00-00.000Z.sql.gz

Dosya orada ve boyutu sıfır değil. Bu sadece yarısı.

Geri yükleme testi

Dokploy'un Backups sekmesinde seçilen yedeği geri yükleyen bir düğme var ve gerçek bir sorunda ilk başvuracağın yer orası olacak. Ama asıl görmek istediğim şey yedeğin Dokploy'dan bağımsız olarak kullanılabildiği. Sunucu tamamen gittiyse elinde sadece S3'teki dosya ve bir PostgreSQL olacak.

Testi üretim sunucusuna dokunmadan, Docker kurulu ayrı bir makinede yapıyorum. Önce dosyayı indirip arşivin sağlam olduğuna bakıyorum:

aws --profile yedek --endpoint-url https://s3.example.com \
  s3 cp s3://dokploy-yedek/duyuru-db/2026-10-18T03-00-00.000Z.sql.gz ./yedek.sql.gz
gunzip -t yedek.sql.gz && echo "arşiv sağlam"
gunzip -c yedek.sql.gz | head -c 5; echo

Son komut dosyanın biçimini söylüyor. Çıktı PGDMP ise pg_dump'ın custom formatı, pg_restore ile açılıyor. -- ya da SET ile başlayan düz metin görüyorsan düz SQL'dir ve psql ile yüklenir.

Geçici bir PostgreSQL açıp yedeği içine yüklüyorum. Sürüm, üretimdekiyle aynı ya da daha yeni olmalı:

docker run -d --name restore-test -e POSTGRES_PASSWORD=degistir-beni postgres:16
until docker exec restore-test pg_isready -h 127.0.0.1 -U postgres >/dev/null 2>&1; do sleep 1; done
docker exec restore-test createdb -U postgres duyuru

# custom format için
gunzip -c yedek.sql.gz | docker exec -i restore-test \
  pg_restore -U postgres -d duyuru --no-owner --no-acl

# düz SQL için
# gunzip -c yedek.sql.gz | docker exec -i restore-test psql -U postgres -d duyuru

--no-owner ve --no-acl, nesneleri test ortamında olmayan duyuru kullanıcısına atamaya çalışmasın diye. Yüklendikten sonra verinin gerçekten orada olduğuna bakıyorum:

docker exec restore-test psql -U postgres -d duyuru -c '\dt'
docker exec restore-test psql -U postgres -d duyuru \
  -c "SELECT count(*), max(olusturulma) FROM duyurular;"
 count |          max
-------+------------------------
  1243 | 2026-10-17 22:41:08+00

Tablo adları uygulamana göre değişir. Baktığım iki şey var: tablolar boş değil ve en son kaydın zamanı yedeğin alındığı saate yakın. Satır sayısı sıfırsa ya da en son kayıt üç hafta öncesine aitse yedek alınıyor ama yanlış veritabanından ya da eksik alınıyor demektir. İşte bu, yedeğin sadece "var" olduğuna bakarak görülemeyecek bir hata.

İş bitince temizlik:

docker rm -f restore-test
rm -f yedek.sql.gz

Bu testi bir kez yapıp bırakmıyorum. Ayda bir takvime koyuyorum ve sonucunu bir yere not ediyorum: tarih, hangi yedek, kaç satır, ne kadar sürdü. Geri yükleme süresi de önemli bir bilgi. Veritabanı büyüdükçe artıyor ve bir gün "sistemi ne zaman geri getirirsiniz" sorusunun cevabı olacak.

  • Yedek dosyası S3'te ve boyutu makul
  • Arşiv gunzip -t testinden geçiyor
  • Yedek temiz bir PostgreSQL'e hatasız yükleniyor
  • En son kaydın zamanı beklenen aralıkta
  • Geri yükleme süresi not edildi

Veritabanı yedeği her şeyi kapsamıyor. Uygulama kullanıcıların yüklediği dosyaları bir volume'da tutuyorsa onlar bu yedekte yok. Dokploy'un kendi ayarları, yani projeler, alan adları ve ortam değişkenleri de kendi PostgreSQL'inde duruyor. Dokploy'da hem volume'lar hem de panelin kendi veritabanı için yedek seçenekleri var, ikisini de aynı S3 hedefine yönlendirmek ve en az bir kez geri yüklemeyi denemek gerekiyor.

Geri yükleme testini bir kere yapıp bırakma. Takvime koy, üç ayda bir de olsa tekrarla.