Sıfırdan Production’a: Bir Self-Hosted PaaS Mimarisi Kurma Hikayemiz
Coolify, Tailscale ve Ansible üçlüsüyle kendi multi-tenant deploy altyapımızı nasıl kurduk, yolda hangi duvarlara çarptık, neler öğrendik.
Sıfırdan Production’a: Bir Self-Hosted PaaS Mimarisi Kurma Hikayemiz
Coolify, Tailscale ve Ansible üçlüsüyle kendi multi-tenant deploy altyapımızı nasıl kurduk, yolda hangi duvarlara çarptık, neler öğrendik.

Bir yazılım firması olarak yıllar boyunca müşteri uygulamalarımızı dış bulut sağlayıcılarında host ettik. Her şey çalışıyordu, ama her ay sonu ekibimiz iki tip soruyla karşılaşıyordu: birincisi “neden bu kadar fatura ödüyoruz”, ikincisi “neden her müşteriyi farklı yönetiyoruz”. İlki cebimizi, ikincisi zamanımızı yiyordu.
Geçtiğimiz aylarda proje ve ekip lideri olarak ekibe ve yönetime bir öneri götürdüm: müşteri uygulamalarımızı kendi yönettiğimiz VDS’lere taşıyalım, ve bunu yaparken bir daha “her müşteriyi farklı yönetiyoruz” cümlesini kurmayacağımız bir altyapı çıkaralım. Hedef üç maddeydi: tek panelden tüm müşterilerimizi yönetebilelim, yeni bir müşteri için VDS açtığımızda 10 dakika içinde production-ready hale gelsin, ve yönetim trafiğimiz public dünyada görünür olmasın.
Süreç düşündüğümüzden uzun sürdü, beklemediğimiz noktalarda duvarlara çarptık, ama sonunda müşteri sunucularını aynı altyapı üzerinden yönetir hale getirdik. Bu yazıda hem mimari kararlarımızı hem de yolda öğrendiklerimizi paylaşacağız. Amacımız ders kitabı yazmak değil — aynı problemi çözmeye çalışan başka bir ekibe faydalı olabilmek…
Mimari Karar: Üç Bileşen Hangi Sorunu Çözüyor?
Self-hosted PaaS denildiğinde insanın aklına genelde tek bir araç geliyor. Bizim ihtiyaçlarımız tek bir araçla karşılanmıyordu, üç farklı katmanın birbirinin eksiğini tamamladığı bir yapı kurduk.
İlk katman Coolify… Benimde en çok sevdiğim ve kullandığım PaaS olur kendileri…
Müşteri başına ayrı VDS kullanıyoruz; bu kararı regülasyon, izolasyon ve müşteri psikolojisi gerekçeleriyle çok önceden vermiştik. Her VDS’e ayrı Coolify kurmak operasyonel olarak çıkmaz sokaktı. Coolify multi-server desteği veriyor: tek bir master Coolify instance’ı, uzak sunuculara SSH ile bağlanıp Docker’ı kuruyor, Traefik’i yönetiyor, container’ları orchestrate ediyor. Müşteri sayımız arttıkça yönetim yükü doğrusal artmıyor — bu en çok ihtiyacımız olan özellikti.
İkinci katman Ansible. Bir VDS’i production’a hazır hale getirmek 30'dan fazla adım gerektiriyor: SSH’i sertleştirmek, UFW kurallarını yazmak,çeşitli güvenlik araçlarını kurmak, time sync’i ayarlamak, otomatik güvenlik güncellemelerini etkinleştirmek, backup’ı yapılandırmak… Yazarken bile darlandım… Bu adımları beş kez elle tekrarlamak hem zaman kaybı hem de kaçınılmaz olarak sürüklenme yaratıyor — ekibimizden biri sunucu 4'te yaptığı bir şeyi sunucu 1'de unutsa, bunu haftalar sonra fark ediyoruz. Ansible role’leri her sunucuyu birbirinin ikizi yapıyor, ve idempotent yapısı sayesinde aynı playbook’u tekrar çalıştırmak hiçbir zarar vermiyor.
Üçüncü katman Tailscale. “Yönetim panelimiz public internet’te açıkta olmasın ama yine de istediğimiz an erişebilelim” probleminin temiz cevabı oldu. Coolify panel’i (port 8000) sadece Tailscale ağında görünüyor; SSH yönetimi de öyle. Public internete sadece müşteri uygulamalarının HTTPS portları açık. Yönetim trafiğimiz Tailscale tüneli içinde kalıyor.
Bu üç katmanın birinden vazgeçmek diğerlerini de bozar. Ansible olmasa Coolify’a uzak sunucu eklemek için her seferinde aynı 30 adımı tekrarlamak gerekirdi. Tailscale olmasa Coolify panel’ini public IP’de açmak veya VPN kurmak zorunda kalırdık. Coolify olmasa Docker compose ve Traefik’i her sunucuda elle yönetirdik. Üçü birlikte ekibimizin “müşteri başına 10 dakika” hedefini mümkün kıldı.
Ansible Role Yapısı
Bir VDS’i sıfırdan production’a hazırlamak için ekibimizin yazdığı role’leri dört katmana ayırdık.
Hardening role’ü; SSH’i 2222 portuna taşıyor, root login’i kapatıyor, key-only authentication zorunlu kılıyor, admin user’ı oluşturup, public key’leri yazıyor, hostname ve timezone ayarını yapıyor. UFW kurallarını default deny + 2222/80/443 allow şeklinde kuruyor ve Docker bridge subnet’lerini özellikle ekliyor (Coolify’ın localhost SSH özelliği bu subnet’lere ihtiyaç duyuyor). Sağlayıcı network’lerinde NTP UDP’sinin bloklu olduğu durumlar için systemd-timesyncd yerine htpdate cron’u devreye alıyor.
CrowdSec role’ü; Community edition’ı kuruyor, Central API’ye kayıt ediyor (community blocklist’e erişim için), sonra LAPI portunu 8080'den 8181'e taşıyor. Bu son adım kritik bir öğrenmenin sonucu: Coolify’ın Traefik’i dashboard’unu 8080'de açıyor, çakışma her iki tarafı da çalışmaz hale getiriyor. İlk sunucumuzda fark ettik, role’e kalıcı eklemeye karar verdik.
Tailscale role’ü;
Başta Tailscale’in resmi install.sh script'ini kullanıyordu. Ubuntu 24.04 (noble) çıktığında script bir süre boyunca "OS not yet supported" yanıtı vermeye başladı, halbuki apt repo destekliyordu. Ekip olarak role'ü doğrudan apt repo eklemeye dönüştürdük:
- name: Install Tailscale apt signing key
get_url:
url: "https://pkgs.tailscale.com/stable/{{ ansible_distribution|lower }}/{{ ansible_distribution_release|lower }}.noarmor.gpg"
dest: /usr/share/keyrings/tailscale-archive-keyring.gpg
- name: Install Tailscale apt source list
get_url:
url: "https://pkgs.tailscale.com/stable/.../tailscale.list"
dest: /etc/apt/sources.list.d/tailscale.list
- name: Install Tailscale package
apt:
name: tailscale
update_cache: yes
Bu yaklaşım distro-bağımsız ve script güncellemesi beklemekten kurtardı. Sonra tailscale up --auth-key=... --hostname=... --ssh komutuyla, vault'ta tutulan reusable auth key üzerinden parolasız Tailscale'e katılım gerçekleşiyor.
Restic backup role’ü; Günlük cron ile S3 uyumlu storage’a yedek alıyor.
İlk sunucumuzu Ansible’la kurduğumuzda 8 dakika sürdü. İkinci sunucu da benzer süre. Beşincisinde başka konularda zorlandık ama Ansible kendi kısmında güvenilir oldu.
Inventory Yaklaşımımız
inventory/hosts.yml her sunucu için tek blok:
customers:
hosts:
customer-a:
ansible_host: customer-a-vds # Tailscale magic DNS
ansible_user: admin
ansible_port: 22 # Tailscale SSH
customer_name: customer-a
tailscale_hostname: customer-a-vds
has_database: true
Bootstrap öncesi geçici olarak ansible_host: <public-ip>, ansible_user: root, ansible_port: 22 (image'in fabrika ayarı). Hardening tamamlanınca yukarıdaki kalıcı state'e geçiyor. Aynı playbook'u defalarca çalıştırabiliyoruz, sadece eksik kalan task'ları işliyor.
Multi-Stage Dockerfile Pattern’i: Beş Repo, Aynı İskelet
Müşteri repo’larımız çok çeşitli stack’lerde geldi:
- Bun + Hono API + Vue admin, pnpm monorepo, Postgres
- Aynı patternde üç servisli (api/admin/partner) bir başkası
- Express API + React Vite, npm workspaces, Postgres
- Bun + Hono + Vue admin, pnpm
- Express + Mongoose, React Vite + Astro static, MongoDB
Hepsine aynı production Dockerfile pattern’ini uygulamaya karar verdik. Çeşitlilik içinde tek bir kalıp tutturmak, ekibimizin her repo’da aynı zihinsel modeli kullanmasını sağladı:
deps stage → workspace deps (npm/pnpm install once)
↓
build-shared → shared-types, api-client gibi internal paketler
↓
build-<app> → app başına ayrı build (api / admin / web)
↓
runtime-<target> → her app için ayrı runtime image
Bu yapının kazandırdığı şey, üç container build’ini tek workspace install’ından üretmek oldu. Üçü ayrı npm install çekseydi gigabytes indirir ve OOM olurduk. Fakat formülümüz belliydi;
Tek install + build artifact paylaşımı = hızlı build + düşük bellek.
Healthcheck’lerde Bir Günlük Fail: BusyBox wget IPv6 Problemi
Her repoda aynı healthcheck pattern’ini kullanıyorduk:
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
Container’lar başlıyordu, application logları normaldi, ama Coolify “unhealthy” gösteriyordu.
docker exec ile container'a girip elle test ettiğimiz bunu gördük:
$ wget --spider http://localhost:3000/health
Connecting to localhost:3000 ([::1]:3000)
wget: can't connect to remote host: Connection refused
wget IPv6'ya bağlanmaya çalışıyor ([::1]), ama API server 0.0.0.0:3000 (yalnızca IPv4) bind ediyor. Alpine'in BusyBox wget'i localhost'u IPv6 önceliklendirip resolve ediyor ve fail oluyor:
$ wget --spider http://127.0.0.1:3000/health
{"status":"ok"}
Çözüm tüm Dockerfile ve compose healthcheck’lerinde localhost yerine 127.0.0.1 kullanmak oldu. Bu öğrenme geriye dönük olarak çok kolay görünüyor, ama bilinmediği için saatler kaybediyorsunuz.
Coolify “Magic Env”i: SERVICE_FQDN
Coolify’ın en güzel feature’larından biri:
api:
environment:
SERVICE_FQDN_API_3000: /
Compose’da bu satır olunca Coolify otomatik bir sslip.io subdomain’i üretiyor ve Traefik label’ı ekliyor. Domain almadan, DNS yayılması beklemeden, sunucu kullanıma açılır açılmaz <random>-<id>.<server-ip>.sslip.io adresinde HTTPS olarak servis çalışıyor.
Çalıştığında muhteşem. Bir repomuzda Coolify nedense sadece web servisi için subdomain üretti, api için üretmedi. docker_compose_domains field'ı {"web": {...}} döndü, api yok. SERVICE_FQDN_API_3000 env'ini API üzerinden manuel eklediğimizde de Coolify bunu Traefik label'ına yansıtmadı. Coolify'ın v4 API'si compose application yönetimi için yeterince olgun değil; bazı action'lar hâlâ sadece UI'dan çalışıyor.
Ekibimiz bu durumda panel’den manuel intervention yapıyor: app sayfası → Configuration → Services → api → Domains alanı.
Farklı Duvarlar
Bu yazının en faydalı kısmı belki burası. Her sunucumuzda farklı bir duvarla karşılaştık.
Sağlayıcı Image’inin /run/sshd Dramı
İlk Ansible playbook’umuz çalışırken hardening role’ünün SSH config deploy task’ı patladı:
Missing privilege separation directory: /run/sshd
Sağlayıcının image’inde sshd çalışıyor ama /run/sshd dizini yok. Ubuntu 24.04 systemd-tmpfiles bunu boot'ta oluşturmalı, ama image build'inde oluşturulmamış. Ansible config push ettiğinde sshd reload etti, reload yeni privilege separation dizini istedi, yok. Service crashed.
Şanslıydık — eğer son task SSH config olsaydı bağlantımız kopabilirdi. Hardening role içinde başka task’lar bunu önceledi, recovery’e zaman bulduk.
Role’e tek satır eklenerek kalıcı çözdük:
- name: Ensure /run/sshd exists (privilege separation directory)
file:
path: /run/sshd
state: directory
Sağlayıcı image’lerinde bunun olmaması bug mı? Belki. Standart Ubuntu cloud image’inde böyle bir sorun yok. Ama bizim kullandığımız sağlayıcıda var, ve role artık bunu garanti ediyor.
Vue Admin Build’inin Dört Farklı Şekilde Patlaması
Bu repo pnpm + Vue + Vite. Production build hatalarımız:
Birinci hata: Boilerplate @{{PROJECT_NAME}}/shared-types import'ları dört farklı dosyada. Template'ten kalmış placeholder'lar. Dev mode'da kimse fark etmemiş çünkü bu dosyaların sadece bir kısmı yükleniyormuş. Production build'de tsc patladı. Beş dosyada find-and-replace ile çözüldü..
İkinci hata: Test fixture’larda User.status alanı eksik. User tipi sonradan güncellenmiş, mock'lar eski kalmış. tsc'nin vue-tsc -b && vite build chain'inde tip kontrolü test'leri de kapsıyor.
Çözümümüz: prod build script'inden vue-tsc -b'yi çıkarmak, sadece vite build bırakmak. Tip kontrolü ayrı build:check script'ine taşındı; CI'da çalışacak, prod build'i engellemez hale geldi.
Üçüncü hata: Baseline Prisma migration eksikti. prisma/migrations/ dizininde sadece migration_lock.toml vardı, schema'yı uygulayan SQL yoktu. İlk deploy'da prisma migrate deploy çalıştı ve "no pending migrations" dedi. Container ayağa kalktı, ama schema oluşturulmadığı için ilk request'te relation "User" does not exist patladı. Lokalde prisma migrate diff --from-empty --to-schema ile baseline'ı ürettik, commit'ledik. Şimdi sıfır DB'ye deploy ettiğimizde schema otomatik kuruluyor.
Dördüncü hata: api-client paketi runtime’da import ediliyordu ama Dockerfile’a kopyalanmamıştı. Workspace’in bağımlı olduğu internal package’ı build aşamasında dist’i oluşturmak gerekiyordu. Düzelttik, push ettik, redeploy.
Bu dört adımı geçirmek 4 saatimize mal oldu. Ekipçe vardığımız sonuç: production’a push etmeden önce docker build . lokalde tüm hedefler için bir kez başarılı çalışmadıkça commit'lemiyoruz. "Lokalde çalışıyor → CI'da çalışıyor → prod'da çalışıyor" yanlış; doğrusu "lokal Docker'da çalışıyor → prod'da çalışıyor."
TypeScript ve Kütüphane Uyumsuzluğunun Derslik Kalitesi Örneği
Uygulama API’si Express 4 + @types/express@4 kullanıyor. Repo'ya express-rate-limit@8 eklenmiş — ki v8 Express 5'i hedefleyen tipler ile geliyor. tsc patladı:
TS2769: Argument of type 'RateLimitRequestHandler' is not assignable to parameter of type 'Application'
İlk içgüdü as any cast oldu. İkincisi daha kötü: tsconfig.json'a strict: false. Üçüncüsü tsc --noCheck (tip kontrolünü tamamen atla, sadece JS emit et).
Hepsi geçici çözümdü. Doğru çözüm express-rate-limit'i v7'ye düşürmekti. v7 Express 4 ile uyumlu, runtime davranışı aynı, sadece tipler eski sürümü hedefliyor. package.json'da bir satır:
"express-rate-limit": "^7.4.1"
Sonra tsc temizlendi. Ama bir başka tip hatası daha çıktı: swagger-ui-express'in kendi getirdiği @types/express-serve-static-core versiyonu, ana @types/express'inkinden farklıydı. npm overrides ile root'tan dedupe yapamıyorduk çünkü swagger-ui-express sub-dependency olarak kendi tipini getiriyor. Bu satıra @ts-expect-error koyduk:
// @ts-expect-error swagger-ui-express vs @types/express RequestHandler divergence
app.use('/api/docs', ...swaggerUi.serve, swaggerUi.setup(spec, opts));
@ts-expect-error as any'den daha temiz, çünkü directive sadece o satırda etki gösteriyor, tipler düzeltilirse derleyici "unused directive" hatası veriyor ve directive'i kaldırmamızı söylüyor, ve cast değil — "burada tip sistemini bilerek bypass ediyoruz, runtime sağlam" beyanı için…
İki ders aldık. Birincisi: major version dependency bump’larını sadece test’ler geçti diye merge’lemiyoruz; breaking type değişiklikleri runtime’da yakalanmıyor. İkincisi: as any neredeyse hep yanlış cevap, @ts-expect-error neredeyse hep daha doğru ifade.
2. Sunucu: Şanslı Durum
İkinci sunucumuzun repo’su daha modern hazırlanmıştı. Kendi Coolify-ready compose’u vardı, healthcheck’lerinde zaten 127.0.0.1 kullanılmıştı. Tek "preventive fix" olarak ilk sunucumuzdan öğrendiklerimizi commit ettik ve push ettik. Deploy ilk seferde geçti.
Bunu yazma sebebimiz: tüm sunuculara aynı yara açılmıyor. Bir repo “production-grade Dockerfile” ile geliyorsa bir günlük debug oturumunu ortadan kaldırabiliyor. İlk sunucu için yaşadıklarımızı ekibimize “checklist” olarak verdik, sonraki repo’larda baştan doğru yapma alışkanlığı oluştu.
5. Sunucu: 24 Saatte Üç Ayrı Duvarın Hikayesi
Beşinci sunucumuz, sürecin en zor müşterisi oldu.
Birinci duvar: Tailscale install.sh “ubuntu noble isn’t supported by this script yet” dedi. Önceki dört sunucu hep geçmişti, beşincide patladı. Tailscale’in install.sh’ı upstream cache veya release timeline farkıyla bazen geri kalıyor. Apt repo’ya geçtik (yukarıda anlattığımız çözüm), role’ü kalıcı düzelttik.
İkinci duvar: Bootstrap’i tekrar çalıştırdığımızda Permission denied (publickey). Hardening sırasında SSH key'i admin user'ın authorized_keys'ine eklenmişti, ama proje liderinin Mac'inde o key'in private yarısı yoktu. Diğer sunuculara Tailscale magic DNS ile bağlandığımız için SSH key kontrolünü Tailscale ACL'i atlatıyordu, fark etmemiştik. Beşinci sunucu Tailscale'e bağlanamadığı için public IP üzerinden SSH gerekli oldu. Public path'te key auth zorunlu, key yok, giriş yok.
Çözümümüz Mac’te yeni SSH key oluşturmak (ssh-keygen -t ed25519) ve bu key'in public yarısını admin_ssh_keys listesine eklemek oldu. Sonra tüm sunuculara --tags hardening ile retroactive olarak yansıttık. Şimdi her sunucuya hem Tailscale hem public IP üzerinden direkt erişim var. Tailscale herhangi bir nedenle çökerse acil müdahale yolu açık.
Üçüncü duvar: Sunucuya panel-side reformat sonrası bootstrap yeniden denedik, Permission denied (publickey) yine. Verbose SSH output:
agent contains no identities
Will attempt key: /Users/.../.ssh/id_ed25519
no such identity: ... No such file or directory
Mac tarafında key dosyası yoktu. Format öncesi sunucuya yazılmış key’in private yarısı, key oluşturma sırasında yeniden generate edilmiş ve eski versiyonu vault’a yazılmamıştı. Sağlayıcının panel’inde noVNC yoktu, sadece HTML5 web console — ve console Türkçe klavye layout’undaydı, paste çalışmıyordu. Layout’u loadkeys us ile US'e çevirdik, format sonrası fresh root + bootstrap akışını tekrarladık, sunucu temiz çıktı.
Bu üç duvarın toplamı 4 saat. Aldığımız ders: ekipteki herkesin SSH key’ini en başında admin_ssh_keys listesine ekliyoruz, hangi key'in private yarısının kimde olduğunu açık dökümantasyon halinde tutuyoruz. Bir kişiye bağımlı olmayan acil erişim hattı şart.
Coolify Env Yönetimi
Coolify env’lerini API üzerinden yönetmek beklediğimizden çetrefilliydi.
PATCH /api/v1/applications/{uuid}/envs mevcut env'i günceller. Eğer env yoksa? Sessizce value: "" döndürüyor, hiçbir şey yapmıyor. Yeni env eklemek için POST gerekiyor. Bu davranış dökümante değil, ekibimiz deneme yanılma ile öğrendi.
Bir başka detay: compose dosyasındaki ${VAR} interpolasyonu Coolify'ın is_buildtime: true env'lerini bir env-file'a yazıp docker compose build'e geçiriyor. is_buildtime: false olan env'ler runtime'da container'a inject ediliyor ama build sırasında compose interpolation "variable is not set" warning'i fırlatıyor. Build'i kırmıyor ama log'lar uyarılarla doluyor.
Çözümümüz tüm interpolation noktalarına :- default eklemek oldu:
DATABASE_URL: postgresql://${DB_USER:-app}:${DB_PASSWORD:-}@postgres:5432/${DB_NAME:-app}
Build log’u temizlendi, runtime’da Coolify env’leri override etmeye devam etti.
Geriye Bakınca
Geriye bakıp şunu yapardık dediğimiz şey şu;
Tüm production Dockerfile’ları için bir “production checklist” yazardık.
- Healthcheck’lerde
127.0.0.1(BusyBox wget IPv6 sorunu) - Multi-stage workspace deps install once
- Workspace internal package’lar source COPY edilmiş (api-client, shared-types)
- Prisma generate için build-arg DATABASE_URL placeholder
- Baseline migration
prisma/migrations/<ts>_init/migration.sqlrepo'da - Build script’te
tscyalnızca CI için, prod build sadece bundler (vite build,bun build) - npm
overrides@types/expressdeduplikasyon (Express + tip-uyumsuz lib kullanan repolar)
Bu checklist her repo’nun CONTRIBUTING.md'sine yazılmalı. Deploy hizmeti veren ekip olarak repo'lara PR atmalıydık. İlerleyen müşteriler için bu disiplini kuracağız.
Bu yazıyı yazma sebebimiz aynı problemi çözmeye çalışan başka bir ekibe ya da geliştirici arkadaşlarımıza tecrübemizi paylaşmak... Özetle başlangıçta bilseydik hafta sonumuzu daha az yıpratacak şeyler:
- Bazı VDS sağlayıcılarında public IP üzerinden VDS-to-VDS bağlantı çalışmaz; Coolify multi-server kurulumu Tailscale şart.
- BusyBox wget
localhost'u IPv6 önceliklendirir; Docker healthcheck'lerde127.0.0.1zorunlu. - Coolify domain validation Tailscale IP’li server’da rahatsızlık çıkarır; baştan disable edin veya panel’den manuel domain set etmeye hazır olun.
npm installMac'te bazen esbuild version mismatch'le patlar; Dockerfile'ınpm ciyerinenpm installile esnek tutun.- Major version dependency bump’ları çoğu zaman tip uyumsuzluğu getirir;
@ts-expect-errorneredeyse hepas any'den iyi cevap. - Ansible role’leri 30+ adımlık manual setup’ı reproducible hale getiriyor; bir günlük yatırım, ömür boyu kazanım.
Aynı yolu yürümek isteyen başka bir ekip için: Coolify multi-server + Tailscale topolojisinde referans implementasyon arıyorsanız, bu yazıdaki Ansible role yapısı, Dockerfile pattern’i ve healthcheck/IPv6 dersleri başlangıç noktası…
Başından sonuna kadar liderlik ettiğim, her adımını kurgulayıp, yönettiğim bu süreçteki tecrübelerim umarım sizler için faydalı olur…
Yorumlarınızı, ekleyeceklerinizi, “şuradan farklı yapardım” katkılarınızı duymak isteriz.
메타데이터
- post_id
- 434eeecd2cf3
- slug
- sıfırdan-productiona-bir-self-hosted-paas-mimarisi-kurma-hikayemiz-434eeecd2cf3
- url
- https://medium.com/@abdullahtas/s%C4%B1f%C4%B1rdan-productiona-bir-self-hosted-paas-mimarisi-kurma-hikayemiz-434eeecd2cf3
- canonical_url
- https://medium.com/@abdullahtas/s%C4%B1f%C4%B1rdan-productiona-bir-self-hosted-paas-mimarisi-kurma-hikayemiz-434eeecd2cf3
- author_url
- https://medium.com/@abdullahtas
- status
- ok
- fetched_at
- 2026-06-17 10:21:25