Kendi ürünlerimi geliştirirken veya tek kişilik bir ekiple hızlıca bir şeyleri ayağa kaldırırken, CI/CD (Continuous Integration/Continuous Deployment) süreçlerine bakış açım genelde çok farklı oluyor. Kurumsal projelerde gördüğümüz o devasa, katmanlı, her detayı düşünülmüş pipeline’lar, indie hacker dünyasında çoğu zaman gereksiz bir yük. Bence bir indie hacker için CI/CD’nin temel amacı, benim zamanımı çalmadan, beni tekrar eden işlerden kurtararak hızlıca değer üretmek olmalı.
Geçenlerde kendi finansal hesaplayıcılarım için geliştirdiğim bir yan ürünümün altyapısını güncellerken, CI/CD pipeline’ımı olabildiğince basitleştirmeye çalıştım. Bu süreçte karşılaştığım ve tecrübe ettiğim bazı noktaları seninle paylaşmak istiyorum. Amacım, “en iyi” CI/CD’yi kurmak değil, “benim için en iyi” olanı, yani sürdürülebilir, ucuz ve az bakım gerektiren bir akışı nasıl sağladığımı anlatmak.
Basit Bir CI/CD Akışı Nasıl Olmalı?
Bir indie hacker olarak, her şeyden önce hızlı iterasyon yapmam gerekiyor. Bir fikri test etmek, kullanıcı geri bildirimlerini almak ve ürünü sürekli geliştirmek hayati önem taşıyor. Bu yüzden CI/CD akışımın beni yavaşlatmaması, aksine işimi kolaylaştırması şart. Benim için basit bir CI/CD akışı, kritik adımları otomatize ederken, gereksiz karmaşıklıktan kaçınan bir yapıda olmalı.
Temelde, kodumu yazdığımda otomatik testlerin çalışması, ardından uygulamanın derlenip bir yere dağıtılması benim için yeterli oluyor. Bu adımların ötesindeki her katman, genellikle projenin ölçeği büyüdükçe veya ekip sayısı arttıkça anlam kazanıyor. Küçük ölçekli projelerde, “manuel” görünebilecek bazı adımları bilinçli olarak otomasyon dışı bırakmak, toplam maliyeti (hem zaman hem para) ciddi anlamda düşürebiliyor. Örneğin, bir üretim ERP’sinde her commit’te onlarca entegrasyon testi çalışırken, kendi yan ürünümde sadece unit ve birkaç entegrasyon testi ile yetiniyorum. Bu, bana hem hız hem de maliyet avantajı sağlıyor.
Hızlı ve Otomatik Testler
Testler, CI/CD’nin kalbi. Ben genellikle bir git push yaptığımda hemen testlerin çalışmasını beklerim. Bu, özellikle küçük projelerde, hatayı erkenden yakalamanın en etkili yolu. Eğer testler yavaş çalışıyorsa, bir süre sonra onları çalıştırmaktan vazgeçtiğimi, hatta commit’lerimin kalitesinin düştüğünü fark ettim. Bir müşteri projesinde bunu acı şekilde yaşadım: bir refactoring sırasında testlerin uzun sürmesi yüzünden, her değişikliğimde beklemek yerine lokalde “çalışıyor gibi” görünen kodu push’lamaya başlamıştım. Sonuç: testler patladı ve rollback yapmak zorunda kaldık. Bu yüzden, testlerin hızlı olması benim için birincil öncelik.
Kullandığım test stratejileri genellikle şunları içeriyor:
- Unit testler: Uygulama mantığını en küçük birimlerde test eder, saniyeler içinde biter.
- Entegrasyon testleri: Veritabanı veya API gibi dış bağımlılıklarla etkileşimi test eder, daha yavaş ama yine de makul sürede tamamlanır.
- Linting/Static Analysis: Kod kalitesini ve stilini kontrol eder, build öncesi hataları yakalar.
Otomatik Build ve Artifact Yönetimi
Testler başarılı olduktan sonra, uygulamanın derlenip dağıtıma hazır hale getirilmesi gerekiyor. Benim projelerimin çoğu Docker konteynerleri üzerinde çalıştığı için, bu adım genellikle bir docker build komutundan ibaret oluyor. Kendi yan ürünlerimde, bu derlenmiş imajı doğrudan sunucuma gönderip orada docker compose ile ayağa kaldırıyorum.
Artifact yönetimi konusunda ise, indie hacker’lar için çok karmaşık çözümlere gerek yok. Eğer bir Docker imajı kullanıyorsam, bu imajı ya doğrudan hedef sunucuda derliyorum ya da küçük projelerde Docker Hub gibi ücretsiz bir registry’ye push’luyorum. Kurumsal dünyada Maven reposundan Nexus’a kadar onlarca farklı artifact yönetimi katmanı olsa da, kendi projelerimde bu basitlik bana yetiyor. Önemli olan, build’in tekrarlanabilir olması ve herhangi bir zamanda önceki bir versiyona kolayca dönebilmem.
# Basit bir Docker build ve push süreci
# Benim kendi VPS'imde kullandığım bir örnek
#!/bin/bash
APP_VERSION=$(git rev-parse --short HEAD)
REPO_NAME="my-indie-app"
REGISTRY="my.private.registry.com" # veya Docker Hub
echo "Building Docker image for version: $APP_VERSION"
docker build -t $REGISTRY/$REPO_NAME:$APP_VERSION .
if [ $? -eq 0 ]; then
echo "Image built successfully. Pushing to registry..."
docker push $REGISTRY/$REPO_NAME:$APP_VERSION
echo "Image pushed. Deleting local image to save space."
docker rmi $REGISTRY/$REPO_NAME:$APP_VERSION
else
echo "Docker build failed!"
exit 1
fi
Bu basit script, bana hem sürüm kontrolü sağlıyor hem de gereksiz yer kaplamasını önlüyor. Bir keresinde build sırasında VPS’imdeki disk dolup işlem patlamıştı; sebebi eski imajları temizlemiyor olmamdı. Sondaki docker rmi satırını ekleyerek bu sorunu çözdüm.
Fazla Karmaşıklığın Gizli Maliyetleri
CI/CD’de “daha fazla otomasyon, daha iyi” yanılgısına düşmek, bir indie hacker için pahalıya mal olabilir. Kurumsal dünyada, otomasyonun getirdiği maliyet, genellikle insan kaynağı maliyetinin yanında devede kulak kalır. Ancak tek kişilik bir ekipte, CI/CD pipeline’ını tasarlamak, kurmak ve bakımını yapmak tamamen benim omuzlarımda. Bu da, ürün geliştirmeye ayırmam gereken değerli zamanı direkt olarak benden çalıyor.
Bakım Yükü
Karmaşık bir CI/CD pipeline’ı, zamanla kendi başına bir proje haline gelebilir. Bağımlılık güncellemeleri, araç versiyon farklılıkları, config değişiklikleri derken, “neden patladı bu pipeline?” sorusunun cevabını bulmak ciddi zaman alabiliyor. Örneğin npm install komutunun farklı runner’larda farklı davranması ve cache tutarsızlıkları yüzünden deploy’ların patlaması, debug etmesi sinir bozucu klasik bir senaryodur. Kendi projelerimde bu tür bir zaman kaybı kabul edilemez. O yüzden, olabildiğince az hareketli parça içeren bir sistem kurmaya özen gösteriyorum.
- Bağımlılık Güncellemeleri:
Node.jsveyaPythonversiyonları değiştiğinde, CI/CD ortamının da güncellenmesi gerekir. - Tooling Değişiklikleri: Jenkins’ten GitLab CI’ya geçiş, yeni bir öğrenme eğrisi ve mevcut pipeline’ların yeniden yazılması anlamına gelir.
- Çevresel Faktörler: Build sunucusundaki disk doluluğu, ağ sorunları veya API limitleri gibi dış etkenler.
Kaynak Tüketimi
CI/CD servisleri, genellikle kullandığın kaynaklara (CPU, RAM, depolama) göre ücretlendirilir. GitHub Actions, GitLab CI, CircleCI gibi popüler servisler, belirli bir kullanım limitine kadar ücretsiz olsa da, büyük projelerde veya sık commit yapılan durumlarda bu limitler hızla aşılabilir. Bir yan ürünüm için, her commit’te birden fazla platforma (web, mobil) build almam gerektiğinde, aylık GitHub Actions faturamın beklenmedik seviyelere çıktığını gördüm. Bu durum, özellikle projeniz henüz gelir getirmezken ciddi bir maliyet kalemi oluşturabilir.
Kendi VPS’imde self-hosted runner kullanmak, bu maliyetleri düşürmek için başvurduğum bir yöntem oldu. On-premise kurulumlarda CI/CD sunucularının yoğun CPU tüketimi, doğrudan elektrik ve donanım maliyetine de yansır. Kendi küçük VPS’imde, bu maliyetleri minimumda tutmak için kaynakları verimli kullanmak zorundayım. Örneğin, build cache’lerini akıllıca yönetmek veya sadece değişiklik olan modülleri yeniden derlemek gibi optimizasyonlar yapıyorum.
Öğrenme Eğrisi ve Bağlılık
Her yeni CI/CD aracı veya teknolojisi, beraberinde bir öğrenme eğrisi getirir. Kubernetes’te dağıtım yapmak için Helm chart’ları öğrenmek, Argo CD ile GitOps uygulamak veya farklı bulut sağlayıcılarının CI/CD hizmetlerini entegre etmek, tek kişilik bir ekip için önemli bir zaman yatırımını gerektirir. Bu öğrenme süreci, bazen ürünün temel özelliklerini geliştirmekten daha fazla zamanımı alabiliyor.
Ayrıca, belirli bir CI/CD platformuna veya teknolojisine aşırı bağımlılık (vendor lock-in) da riskler taşıyor. Platform değiştiğinde veya ücretlendirme politikaları değiştiğinde, tüm pipeline’ı yeniden yazmak zorunda kalabilirim. Kendi yan ürünlerimde, bu tür bağımlılıkları minimumda tutmaya çalışıyorum. Mümkün olduğunca standart araçlar (Docker, Bash scriptleri, systemd unit’leri) kullanıyorum ki, bir gün farklı bir VPS sağlayıcısına geçmek istediğimde başım ağrımasın. VPS migration deneyimim yazımda bu geçiş süreçlerinin ne kadar sancılı olabileceğini anlatmıştım.
Benim Indie CI/CD Tercihlerim
Yıllar içinde edindiğim tecrübelerle, indie projelerimde CI/CD için belirli tercih setleri geliştirdim. Bunlar, bana hem hız hem de maliyet avantajı sağlayan, aynı zamanda bakım yükünü minimumda tutan yaklaşımlar.
Git Hook’ları ve Basit Scriptler
Çoğu zaman, en basit çözümler en etkili olanlardır. Kendi projelerimde, git hook’ları ve basit bash scriptleri kullanarak lokal CI’yı sağlıyorum. Örneğin, pre-commit hook’u ile kodumu push etmeden önce lint ve format işlemlerini otomatik olarak çalıştırıyorum. Bu, kirli kodun repository’ye girmesini engelliyor ve remote CI’da daha temiz bir başlangıç yapmamı sağlıyor.
# .git/hooks/pre-commit dosyasına eklediğim basit bir hook örneği
#!/bin/sh
# Staged edilmiş Python dosyalarını lint et
echo "Running flake8 on staged Python files..."
git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | xargs flake8
if [ $? -ne 0 ]; then
echo "Flake8 issues found. Please fix them before committing."
exit 1
fi
# Staged edilmiş dosyaları formatla (örneğin Black ile)
echo "Running black formatter on staged Python files..."
git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | xargs black
if [ $? -ne 0 ]; then
echo "Black formatter failed. Please check."
exit 1
fi
# Testleri çalıştır
echo "Running unit tests..."
pytest --ignore=integration_tests/
if [ $? -ne 0 ]; then
echo "Unit tests failed. Aborting commit."
exit 1
fi
exit 0
Bu hook sayesinde, daha commit anında hataları yakalıyorum ve remote CI’ya göndermeden önce çözüyorum. Bu, bana hem CI süresinden tasarruf sağlıyor hem de hatalı commit’lerin önüne geçiyor. Büyük bir kod tabanında bu tür lokal otomasyonlar, daha commit aşamasında kaliteyi yukarı çekerek remote CI’ya temiz bir başlangıç sağlıyor.
Self-Hosted Runner ve VPS Ekonomisi
Daha önce de bahsettiğim gibi, bulut tabanlı CI/CD servislerinin maliyetleri bazen beklenmedik şekilde artabiliyor. Kendi VPS’imde çalıştırdığım self-hosted runner’lar, bu maliyetleri kontrol altına almamı sağlıyor. Küçük bir VPS (örneğin 2 CPU, 4GB RAM) bile, çoğu indie projesi için birden fazla CI/CD işini paralel çalıştırmaya yetiyor. Bu, özellikle maliyet bilinci yüksek bir indie hacker için önemli bir avantaj.
Kendi systemd unit’leri ile bu runner’ları yönetiyorum. Bir journald ile log takibi, cgroup limitleri ile kaynak kontrolü yaparak, VPS’imin stabil çalışmasını sağlıyorum. Hatalı bir script bellek tüketimini patlattığında cgroup memory.high yumuşak limiti devreye girer ve diğer servisler etkilenmeden runner sınırlanır; bu da sorunu tespit edip çözmek için bana alan bırakır. Bu tür küçük dokunuşlar, sistemin genel dayanıklılığını artırıyor.
Deployment Stratejileri: “Rolling Restart” ve Tek Tuşla Rollback
Indie projelerinde genellikle büyük ölçekli ve karmaşık dağıtım stratejilerine (blue-green, canary) ihtiyacım olmuyor. Benim için en pratik olanı, mevcut Docker konteynerini durdurup yeni versiyonu başlatmak, yani basit bir “rolling restart”. Bu işlem, genellikle birkaç saniye sürüyor ve uygulamanın kısa süreli bir kesinti yaşamasını göze alıyorum.
Ancak, her zaman işler yolunda gitmeyebilir. Bu yüzden, tek tuşla geri alma (rollback) mekanizmasına sahip olmak hayati önem taşıyor. Eğer yeni dağıtımda bir sorun çıkarsa, hızlıca önceki stabil versiyona dönebilmeliyim. Kendi script’lerimde, son başarılı imaj etiketini bir değişkende tutarak, gerektiğinde bu imajı tekrar deploy edebiliyorum.
# Basit bir deploy ve rollback scripti özeti
#!/bin/bash
APP_NAME="my-indie-app"
CURRENT_VERSION=$(cat /path/to/app_version.txt)
NEW_VERSION=$1
if [ -z "$NEW_VERSION" ]; then
echo "Usage: $0 <new_version>"
exit 1
fi
echo "Deploying $APP_NAME version $NEW_VERSION..."
# Yeni imajı çek
docker pull my.private.registry.com/$APP_NAME:$NEW_VERSION
if [ $? -ne 0 ]; then
echo "Failed to pull new image. Aborting deploy."
exit 1
fi
# Mevcut konteyneri durdur ve kaldır
docker compose -f /path/to/docker-compose.yml down
# Yeni versiyonu ayağa kaldır
sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" /path/to/docker-compose.yml # docker-compose.yml'deki imaj tag'ini güncelle
docker compose -f /path/to/docker-compose.yml up -d
if [ $? -eq 0 ]; then
echo "$APP_NAME version $NEW_VERSION deployed successfully."
echo "$NEW_VERSION" > /path/to/app_version.txt
echo "Old version was: $CURRENT_VERSION. You can rollback using: $0 $CURRENT_VERSION"
else
echo "Deployment failed! Rolling back to $CURRENT_VERSION..."
# Rollback için önceki versiyonu tekrar deploy et
sed -i "s/$NEW_VERSION/$CURRENT_VERSION/g" /path/to/docker-compose.yml
docker compose -f /path/to/docker-compose.yml up -d
echo "Rolled back to $CURRENT_VERSION."
exit 1
fi
Bu script, bana güvenli bir deploy ve hızlı bir rollback imkanı sunuyor. Geri dönüşü zor olan ortamlarda her deploy sonrası uzun ve stresli izleme seansları kaçınılmaz olur; hızlı rollback’i garanti altına almak, kendi projelerimde bu stresi minimuma indiriyor.
Monitoring ve Geri Bildirim: Olmazsa Olmazlar
CI/CD süreci ne kadar basit olursa olsun, uygulamanın yayına alındıktan sonra nasıl davrandığını bilmek kritik. Ben, “deploy ettim ve unuttum” yaklaşımını asla benimsemiyorum. Basit ama etkili bir monitoring ve geri bildirim mekanizması, olası sorunları erkenden tespit etmeme yardımcı oluyor.
Log Takibi ve Basit Alertler
Uygulama loglarını düzenli olarak takip etmek, sorun gidermenin ilk adımı. Benim projelerimde, journald ile logları topluyorum ve gerektiğinde grep ile arama yapıyorum. Çok basit bir tail -f /var/log/syslog | grep "ERROR" bile, birçok sorunu anında görmemi sağlıyor. Daha gelişmiş projelerde ise, Promtail ve Loki gibi hafif çözümleri kullanıyorum.
Ayrıca, kritik hatalar için basit alertler kuruyorum. Örneğin, fail2ban gibi araçlarla SSH brute-force denemelerini engellerken, kendi yazdığım basit scriptlerle uygulamanın belirli bir hata kodu döndürmesi durumunda bana email veya Telegram mesajı gönderilmesini sağlıyorum. Veritabanı tarafında bir alarm düştüğünde bu basit bildirim, sorun büyümeden müdahale etme şansı veriyor. Bu tür proaktif önlemler, büyük krizlerin önüne geçiyor.
Performans Metrikleri
Uygulamanın performansını izlemek, kullanıcı deneyimini doğrudan etkileyen bir faktör. CPU kullanımı, bellek tüketimi, disk G/Ç, ağ trafiği ve API yanıt süreleri gibi temel metrikleri takip etmek benim için yeterli oluyor. node_exporter ve Prometheus gibi araçları, kendi VPS’imde kurup kullanıyorum. Grafana ile de basit dashboard’lar oluşturarak genel durumu gözlemliyorum.
Bir yan ürünümün backend’inde, anlamsız bir şekilde Redis bellek kullanımının arttığını gördüm. Metrikleri incelediğimde, belirli bir veri setinin OOM eviction policy yüzünden sürekli Redis’ten atılıp tekrar yüklendiğini fark ettim. Politikayı allkeys-lru yerine volatile-lru olarak değiştirerek bu sorunu çözdüm. Bu tür gözlemler, sadece monitoring ile mümkün oluyor.
Ne Zaman Daha Karmaşık Çözümlere Bakmalı?
Şimdiye kadar bahsettiklerim, indie hacker’ın tek başına veya çok küçük bir ekiple yola çıktığı senaryolar için ideal. Ancak her projenin bir büyüme potansiyeli var. Peki, ne zaman mevcut basit CI/CD akışım yetersiz kalmaya başlar ve daha karmaşık, kurumsal çözümlere yönelmem gerekir?
Ekip Büyüklüğü ve Proje Kapsamı
Ekibiniz büyüdükçe, koordinasyon ve süreçlerin standartlaştırılması daha önemli hale gelir. Birden fazla geliştirici aynı anda farklı özellikler üzerinde çalışırken, her commit’in otomatik olarak entegre edilmesi ve test edilmesi gerekir. Bu durumda, daha gelişmiş bir CI sistemi (örneğin paralel test çalıştırma, branch bazlı pipeline’lar) kaçınılmaz olur. Ekip belli bir büyüklüğü aştığında manuel entegrasyon süreçleri tıkanır; otomatikleştirilmiş, kapsamlı bir CI/CD bu noktada lüks değil zorunluluk haline gelir.
Projenin kapsamı genişledikçe, farklı servisler ve mikroservis mimarileri devreye girebilir. Her servisin kendi CI/CD pipeline’ı olması, genel karmaşıklığı artırsa da, her servisin bağımsız olarak deploy edilmesini sağlar. Bu, monolith vs microservice seçimlerinde sıkça karşılaştığımız bir trade-off’tur. Monolith’ten Mikroservislere Geçiş üzerine yazdığım yazıda bu kararları detaylıca ele almıştım.
Regülasyon ve Güvenlik İhtiyaçları
Finans, sağlık veya kamu gibi regüle edilmiş sektörlerde faaliyet gösteriyorsanız, CI/CD süreçlerinizde belirli güvenlik ve uyumluluk standartlarını karşılamanız gerekebilir. Bu, daha sıkı erişim kontrolleri, güvenlik taramaları (SAST/DAST), audit logları ve onay süreçleri anlamına gelir. Örneğin regüle edilmiş ortamlarda her deploy’un en az iki farklı kişi tarafından onaylanması (four-eyes) sıkça zorunlu tutulur. Bu tür gereksinimler, basit script tabanlı çözümlerin ötesinde, daha kurumsal CI/CD platformlarını gerektirir.
Güvenlik açığı takibi (CVE), bağımlılık taraması ve kernel module blacklist gibi konular, özellikle hassas verilerle çalışırken önemli hale gelir. Bu tür güvenlik kontrollerini CI/CD pipeline’ına entegre etmek, manuel olarak yapmaktan çok daha güvenilir ve otomatiktir.
Sonuç
Bir indie hacker olarak CI/CD’ye bakış açım her zaman “yeterince iyi ve sürdürülebilir” olmak üzerine kurulu. Kurumsal projelerde gördüğümüz o devasa pipeline’lar, kendi küçük ama çevik projelerimde çoğu zaman gereksiz bir yük. Benim deneyimimde, basit git hook’ları, temel bash scriptleri ve kendi VPS’imde çalıştırdığım self-hosted runner’lar, hem maliyetleri düşük tutmamı hem de hızlı iterasyon yapmamı sağladı.
Önemli olan, CI/CD’nin bir amaç değil, bir araç olduğunu unutmamak. Amacımız, hızlıca değer üretmek ve kullanıcılarımıza ulaşmak. Bu yolda, bizi yavaşlatan veya gereksiz yere kaynak tüketen her türlü karmaşıklıktan kaçınmak, bir indie hacker’ın en büyük avantajı olabilir. Unutma, en iyi CI/CD, sana en az zaman ve en çok fayda sağlayan CI/CD’dir.