Docker & DevOps

    Makefile ile Görev Otomasyonu

    Uzun komutları tek kelimeye indiren Makefile hedefleri, değişkenler ve pratik desenler.

    10 dk okuma Güncellendi: 25 Ağustos 2026

    Bir projeye yeni katılan geliştiricinin ilk günü genelde şöyle geçer: README'deki on dört satırlık komut dizisini kopyalar, üçüncüsünde bir bayrağı atlar, dördüncüsünde farklı bir hata alır ve iki saat kaybeder. Oysa aynı projede make setup yazması yeterli olabilirdi. Makefile ile görev otomasyonu tam olarak bunu yapar: projedeki tüm tekrarlayan komutları tek bir dosyada toplar, isimlendirir ve herkesin aynı şekilde çalıştırmasını sağlar.

    Bu rehberde Makefile'ı bir C derleme aracı olarak değil, modern projelerde bir komut kataloğu olarak kullanmayı anlatacağım. Kural sözdizimini, .PHONY hedeflerin neden zorunlu olduğunu, değişken çeşitlerini ve aralarındaki farkı, dosya bağımlılıklarıyla gereksiz işi atlamayı göreceğiz. Sonunda Docker ve dağıtım komutlarını tek satıra indiren gerçek bir Makefile kuracak ve sekmelerden kaynaklanan klasik hataları konuşacağız.

    Makefile Neden Hâlâ Kullanılıyor#

    Makefile 1970'lerden kalma bir araç ve buna rağmen her yeni projede karşına çıkıyor; sebebi basit. Neredeyse her Linux ve macOS sisteminde make zaten kurulu, hiçbir çalışma zamanına ya da paket kurulumuna ihtiyaç duymuyor, sözdizimi tek bir dosyada okunabiliyor ve make <tab><tab> ile hedeflerin listesi çıkıyor. npm scripts, task runner ya da kabuk betikleri de aynı işi görür ama her biri bir dile ya da ekosisteme bağlıdır; Makefile projeyi kullanan herkesin dilinden bağımsızdır.

    İkinci ve daha derin sebep, make'in temelde bir bağımlılık çözücü olmasıdır. Bir hedefin çıktısı, girdilerinden daha yeniyse make o adımı hiç çalıştırmaz. Bu, büyük projelerde derleme süresini dakikalardan saniyelere indirir ve aynı mantık Docker imajı üretmek, varlık dosyası derlemek ya da veri dönüştürmek gibi işlerde de aynı kazancı verir.

    YaklaşımBağımlılıkBağımlılık takibiÖğrenme eğrisi
    Kabuk betikleriYokYokDüşük
    npm scriptsNode.jsYokDüşük
    Makefilemake (hazır)VarOrta
    Özel task runnerKendi çalışma zamanıDeğişirOrta-yüksek

    Temel Sözdizimi: Hedef, Bağımlılık, Komut#

    Bir Makefile kuralı üç parçadan oluşur ve biçim katıdır:

    hedef: bağımlılıklar
    	komut
    

    Buradaki en kritik ayrıntı, komut satırının başında boşluk değil sekme (TAB) karakteri olmasıdır. Bu, Makefile'da yeni başlayanların yaşadığı bir numaralı hatadır ve hata mesajı da yardımcı değildir: Makefile:4: *** missing separator. Stop. Editörünü Makefile için sekme kullanacak biçimde ayarlamak bu sorunu tamamen kapatır.

    Basit bir örnekle başlayalım:

    # Varsayılan hedef: 'make' yazınca çalışır (ilk hedeftir)
    help:
    	@echo "Kullanilabilir komutlar:"
    	@echo "  make kur      - bagimliliklari kur"
    	@echo "  make test     - testleri calistir"
    	@echo "  make lint     - kod kontrolu"
    
    kur:
    	npm ci
    
    test: kur
    	npm run test
    
    lint:
    	npm run lint
    

    make test yazdığında make önce kur hedefini çalıştırır, sonra test hedefini; çünkü test hedefi kur'a bağımlıdır. Komutun başına konan @ işareti, komutun kendisinin ekrana yazdırılmasını engeller — echo satırlarında bu neredeyse her zaman istenir.

    make her komutu ayrı bir kabukta çalıştırır. Bu, aşağıdaki kuralın neden çalışmadığını açıklar:

    # YANLIŞ: cd bir kabukta, npm başka kabukta çalışır
    hatali:
    	cd frontend
    	npm run build
    
    # DOĞRU: tek satırda zincirle
    dogru:
    	cd frontend && npm run build
    

    Değişkenler ve İki Atama Biçimi#

    Makefile'da iki farklı atama vardır ve aralarındaki fark bir gün mutlaka karşına çıkar:

    # = (ertelenmiş): değer, KULLANILDIĞI anda hesaplanır
    TARIH_ERTELENMIS = $(shell date +%Y%m%d-%H%M%S)
    
    # := (anında): değer, TANIMLANDIĞI anda bir kez hesaplanır
    TARIH_ANINDA := $(shell date +%Y%m%d-%H%M%S)
    
    # ?= (varsayılan): değişken zaten tanımlıysa dokunma
    ORTAM ?= development
    
    # += (ekleme)
    BAYRAKLAR := -v
    BAYRAKLAR += --no-cache
    

    = ile tanımlanan bir değişkeni üç ayrı komutta kullanırsan date üç kez çalışır ve üç farklı değer elde edersin — bir imaj etiketi için bu felakettir. Genel kural: kullanabildiğin her yerde := kullan, ertelemeyi yalnızca gerçekten gerekiyorsa tercih et.

    Ortam değişkenleri Makefile içinden doğrudan okunabilir ve komut satırından geçersiz kılınabilir:

    IMAJ    ?= firmaniz/siparis-api
    SURUM   := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
    KAYIT   ?= registry.firmaniz.com
    
    imaj:
    	docker build -t $(KAYIT)/$(IMAJ):$(SURUM) -t $(KAYIT)/$(IMAJ):latest .
    
    gonder: imaj
    	docker push $(KAYIT)/$(IMAJ):$(SURUM)
    	docker push $(KAYIT)/$(IMAJ):latest
    

    ?= sayesinde make imaj KAYIT=localhost:5000 diyerek kayıt adresini tek seferlik değiştirebilirsin; dosyaya dokunman gerekmez. Bu, aynı Makefile'ın hem yerel geliştirmede hem CI ortamında çalışmasını sağlayan en pratik yoldur. Ortamlar arası bu tür farkları düzenli biçimde yönetmenin genel kalıbı için dev, staging ve prod ortam ayrımı yazısına bakabilirsin.

    .PHONY Hedefler: Atlanması En Pahalı Detay#

    make bir hedefi çalıştırmadan önce, o isimde bir dosya olup olmadığına bakar. Dosya varsa ve bağımlılıklarından yeniyse, "yapacak bir şey yok" der ve komutu hiç çalıştırmaz. Projende test adında bir klasör varsa make test sessizce hiçbir şey yapmaz:

    $ make test
    make: 'test' is up to date.
    

    Bu davranışı kapatmak için hedefi sahte (phony) ilan edersin:

    .PHONY: help kur test lint temizle imaj gonder dagit
    

    Kural basit: dosya üretmeyen her hedef .PHONY listesine girmelidir. build, test, clean, install, deploy gibi isimler proje klasörlerinde sık bulunduğu için bu hata sandığından daha çok karşına çıkar. Alternatif olarak her hedefin hemen üstüne tek satır yazabilirsin:

    .PHONY: temizle
    temizle:
    	rm -rf dist/ node_modules/.cache/
    	docker compose down -v
    

    Dosya üreten hedeflerde ise .PHONY kullanmamalısın, çünkü asıl kazanç oradadır:

    # node_modules, package-lock.json'dan yeniyse npm ci hiç çalışmaz
    node_modules: package-lock.json
    	npm ci
    	@touch node_modules
    
    dist/app.js: node_modules $(wildcard src/*.js)
    	npm run build
    

    Burada make dist/app.js komutu, kaynak dosyalar değişmediyse saniyeler yerine milisaniyelerde döner. touch satırı gerekir çünkü npm ci klasörün zaman damgasını her zaman güncellemez.

    Gerçek Bir Proje Makefile'ı#

    Aşağıdaki Makefile, Docker tabanlı bir web uygulamasının günlük ihtiyaçlarının neredeyse tamamını karşılar. Kopyalayıp kendi projene uyarlayabilirsin:

    .DEFAULT_GOAL := help
    .PHONY: help kur baslat durdur log kabuk test lint goc yedek temizle imaj dagit
    
    ORTAM       ?= development
    COMPOSE     := docker compose
    SERVIS      ?= api
    IMAJ        ?= firmaniz/siparis-api
    SURUM       := $(shell git rev-parse --short HEAD 2>/dev/null || echo dev)
    YEDEK_DOSYA := yedek-$(shell date +%Y%m%d-%H%M%S).sql.gz
    
    help:  ## Bu yardım metnini göster
    	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
    	  | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-12s\033[0m %s\n", $$1, $$2}'
    
    kur:  ## İlk kurulum: imajları çek, bağımlılıkları kur
    	$(COMPOSE) pull
    	$(COMPOSE) build
    	$(COMPOSE) run --rm $(SERVIS) npm ci
    
    baslat:  ## Servisleri arka planda başlat
    	$(COMPOSE) up -d
    	@echo "Uygulama hazir: http://localhost:3000"
    
    durdur:  ## Servisleri durdur
    	$(COMPOSE) down
    
    log:  ## Belirtilen servisin loglarını canlı izle (SERVIS=db make log)
    	$(COMPOSE) logs -f --tail=100 $(SERVIS)
    
    kabuk:  ## Konteyner içinde kabuk aç
    	$(COMPOSE) exec $(SERVIS) sh
    
    test:  ## Testleri konteyner içinde çalıştır
    	$(COMPOSE) run --rm -e NODE_ENV=test $(SERVIS) npm run test
    
    lint:  ## Kod kontrolü
    	$(COMPOSE) run --rm $(SERVIS) npm run lint
    
    goc:  ## Veritabanı göçlerini uygula
    	$(COMPOSE) exec $(SERVIS) npm run migrate
    
    yedek:  ## Veritabanı yedeği al
    	$(COMPOSE) exec -T db pg_dump -U postgres siparis | gzip > $(YEDEK_DOSYA)
    	@echo "Yedek alindi: $(YEDEK_DOSYA)"
    
    imaj:  ## Üretim imajını etiketleyerek üret
    	docker build -t $(IMAJ):$(SURUM) -t $(IMAJ):latest .
    
    temizle:  ## Konteynerleri, volume'ları ve önbelleği temizle
    	$(COMPOSE) down -v --remove-orphans
    	rm -rf dist/ .cache/
    

    help hedefindeki tek satırlık awk numarası, her hedefin yanındaki ## açıklama yorumlarını okuyup renkli bir menü üretir. .DEFAULT_GOAL := help sayesinde make yalnız başına çalıştırıldığında bu menüyü gösterir; yani projeye yeni gelen biri hiçbir şey bilmeden make yazıp ne yapabileceğini görür. Bu iki satır, bir Makefile'ı komut listesinden gerçek bir dokümana çevirir.

    Buradaki Compose komutlarını ve servis mantığını daha yakından tanımak istersen Docker Compose kullanımı yazısı temeli veriyor; imaj hedefinin ürettiği imajı küçültmek için de Docker imaj boyutu optimizasyonu ve Dockerfile en iyi pratikler yazılarındaki katman kurallarını uygulayabilirsin.

    Sık Yapılan Hatalar ve Tuzaklar#

    Sekme yerine boşluk kullanmak. Komut satırları sekmeyle başlamalıdır. cat -A Makefile çıktısında sekmeler ^I olarak görünür; şüphelendiğinde bu komutla bakabilirsin. Editörüne .editorconfig üzerinden Makefile için sekme kuralı koymak kalıcı çözümdür.

    .PHONY yazmayı unutmak. Hedef adıyla aynı isimde bir dosya ya da klasör varsa hedef sessizce atlanır. Bu, "komut çalışmıyor ama hata da vermiyor" sınıfının en yaygın sebebidir.

    $ işaretini kaçırmamak. Makefile içinde kabuk değişkeni kullanacaksan $ işaretini ikiye katlaman gerekir; $VAR Makefile değişkeni, $$VAR kabuk değişkenidir. Aynı şekilde awk '{print $1}' içindeki $1 de $$1 olmalıdır.

    Her satırın ayrı kabukta çalıştığını unutmak. cd, export, source gibi komutlar yalnızca kendi satırlarını etkiler. Birden fazla adımı bağlamak için && kullan ya da .ONESHELL: direktifiyle tüm kuralın tek kabukta çalışmasını sağla.

    Hataları yutmak. make bir komut sıfırdan farklı çıkış kodu döndürürse durur — bu iyidir. Ancak - öneki koyarsan hatayı yok sayar. -rm -rf dist/ gibi bilinçli kullanımlar dışında bu öneki kullanma; testlerin başarısız olduğu bir CI adımını sessizce geçirmenin en kolay yolu budur.

    Paralel çalıştırmada bağımlılık atlamak. make -j4 hedefleri paralel çalıştırır. Hedefler arasındaki gerçek bağımlılıkları yazmadıysan, goc hedefi baslat bitmeden çalışır ve veritabanı henüz ayakta olmadığı için patlar. Paralel kullanacaksan bağımlılıkları eksiksiz tanımla.

    Gizli bilgileri Makefile'a gömmek. Parola ve API anahtarını doğrudan değişkene yazma; Makefile sürüm kontrolündedir. Bunun yerine ?= ile dışarıdan alınmasını bekle ya da bir ortam dosyasından oku:

    # .env dosyasını yükle (varsa) ve değişkenleri dışa aktar
    -include .env
    export
    

    Buradaki - öneki, .env dosyası yoksa hata vermemesini sağlar. .env dosyasının sürüm kontrolüne girmediğinden emin ol; bu dosyaların kazara yayınlanmasının sonuçlarını .git klasörü ve .env dosyası ifşası yazısında anlattım.

    Sıkça Sorulan Sorular#

    Makefile sadece C projeleri için mi#

    Hayır. make başlangıçta derleme için tasarlandı ama bugün en yaygın kullanımı, dilden bağımsız bir komut kataloğu olarak hizmet etmesi. Node.js, Python, PHP ve Go projelerinde Docker komutlarını, test çalıştırmayı, göçleri ve dağıtım adımlarını tek isim altında toplamak için kullanılıyor. Bağımlılık takibi özelliği de derleme dışında varlık üretme ve veri dönüştürme gibi işlerde aynı kazancı sağlıyor.

    missing separator hatası neden alıyorum#

    Bu hatanın neredeyse tek sebebi, komut satırının sekme yerine boşlukla başlamasıdır. Makefile, kural gövdelerinde kesinlikle sekme karakteri bekler ve boşlukları kabul etmez. cat -A Makefile ile dosyayı incelediğinde sekmeler ^I olarak görünür; komut satırlarının başında bu işareti görmüyorsan sorun budur. Editörünün Makefile dosyalarında sekmeyi boşluğa çevirmediğinden emin ol.

    make komutum çalışmıyor ama hata da vermiyor#

    Büyük olasılıkla hedef adıyla aynı isimde bir dosya veya klasör var ve make onu güncel sayıyor. Örneğin projende test adında bir klasör varsa make test çalışmaz. Çözüm, dosya üretmeyen tüm hedefleri .PHONY listesine eklemektir. Hangi hedefin neden atlandığını görmek için make -d hedef komutu ayrıntılı karar günlüğü verir.

    Makefile mı npm scripts mi kullanmalıyım#

    İkisi rakip değil, birlikte iyi çalışır. package.json içindeki scriptler dile özgü işleri (test, lint, build) tanımlamak için doğaldır; Makefile ise bu scriptleri, Docker komutlarını, veritabanı göçlerini ve dağıtım adımlarını tek çatı altında toplar. Projede birden fazla dil veya servis varsa Makefile açık ara daha uygundur, çünkü tek bir giriş noktası sunar ve Node.js kurulu olmayan bir ortamda da çalışır.

    Makefile'da ortam değişkeni nasıl kullanırım#

    Ortam değişkenleri doğrudan $(DEGISKEN) biçiminde okunabilir. Bir varsayılan tanımlamak istiyorsan DEGISKEN ?= varsayilan yazarsın; bu, değişken dışarıdan gelmişse ona dokunmaz. Komut satırından geçici olarak değiştirmek için make hedef DEGISKEN=deger yeterlidir. Bir .env dosyasından toplu yükleme yapmak istersen -include .env ve export satırlarını dosyanın başına ekleyebilirsin.

    Makefile hedeflerini nasıl listelerim#

    En kullanışlı yöntem, her hedefin yanına ## açıklama yorumu yazıp bunları okuyan bir help hedefi eklemektir; böylece make yalnız başına çalıştırıldığında düzgün bir menü çıkar. Hızlı bir çözüm istersen make -qp çıktısını süzebilir ya da çoğu kabukta make yazıp iki kez sekme tuşuna basarak tamamlama listesini görebilirsin. .DEFAULT_GOAL := help satırı bu deneyimi kalıcı hâle getirir.

    Kapanış#

    Bir Makefile, projenin kurumsal hafızasıdır: "bunu nasıl çalıştırıyorduk" sorusunun cevabını README'nin derinlerinden çıkarıp komut satırına taşır. Aklında tutman gereken dört alışkanlık şu: komut satırlarını sekmeyle başlat, dosya üretmeyen her hedefi .PHONY yap, değişkenlerde := varsayılanını benimse ve her hedefe ## açıklaması yazıp bir help hedefi ekle. Bu dördü, dosyanı hem çalışır hem okunur tutar.

    Otomasyon ancak üzerinde çalıştığı altyapı öngörülebilirse işe yarar. Kendi derleme ve dağıtım akışını kurabileceğin tam yetkili ortam için VDS ve bulut sunucu paketlerimize bakabilir, kurulum ve bakım yükünü devretmek isterseniz sunucu yönetimi hizmetimizden yararlanabilirsiniz.

    MakefileOtomasyonBuild

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.