COPY ve ADD işlevsel olarak benzer talimatlardır. COPY, build context'indeki ya da çok aşamalı build'deki başka bir aşamadaki dosyaları imaja kopyalar; ADD buna ek olarak uzak HTTPS ve Git adreslerinden dosya çekebilir ve build context'ten eklenen tar dosyalarını otomatik açar. Yerel dosya kopyalamak için COPY, uzak bir dosyayı build'e indirmek için ise sağlama toplamıyla birlikte ADD kullanmak belgelerin önerdiği yaklaşımdır.

Bu yazıda iki talimatın farklarını, dosyaları kalıcı katman oluşturmadan kullanmanızı sağlayan bind mount alternatifini, önbelleğin kopyalanan dosyaları nasıl karşılaştırdığını, sahiplik ve izin ayarlarını ve sık yapılan hataları anlatıyoruz. Katman önbelleği ve multi-stage temelleri için Dockerfile yazma rehberine, serinin geri kalanı için Docker öğrenme rehberi sayfasına bakabilirsiniz.

İki talimatın kısa karşılaştırması

İki talimatın kısa karşılaştırması: Build context'ten dosya kopyalama, Başka bir aşamadan veya imajdan kopyalama (--from), Uzak HTTPS adresinden dosya çekme, Git deposu çekme
ÖzellikCOPYADD
Build context'ten dosya kopyalamaEvetEvet
Başka bir aşamadan veya imajdan kopyalama (--from)EvetBelgelerdeki örneklerde COPY ile gösterilir
Uzak HTTPS adresinden dosya çekmeHayırEvet
Git deposu çekmeHayırEvet, dal, etiket ve alt dizin seçilebilir
Context'ten eklenen tar dosyasını açmaHayırEvet, otomatik
Uzak kaynak için sağlama toplamı doğrulamaHayırEvet, --checksum ile

Tablodaki farklar, neden çoğu durumda COPY tercih edildiğini de açıklar: COPY yalnızca kopyalar, sürpriz yapmaz. ADD ise bir dosyanın uzantısına ya da kaynağın adresine göre ek işlemler yapabildiği için niyetinizi okuyana daha az açık anlatır. Ek özelliğe gerçekten ihtiyacınız olduğunda ADD seçmek, Dockerfile'ı okuyan kişiye "burada bir indirme ya da açma işlemi var" mesajını verir.

COPY ne zaman doğru seçimdir?

Belgeler COPY talimatını çoğunlukla çok aşamalı build'de bir aşamadan diğerine dosya taşımak için kullanacağınızı söyler. COPY --from yalnızca Dockerfile içindeki aşamalarla sınırlı değildir; yerel bir imaj adı, yerelde ya da bir registry'de bulunan bir etiket de verebilirsiniz. Docker istemcisi gerekirse imajı çeker ve dosyayı oradan kopyalar:

dockerfile
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

Çok aşamalı build belgesindeki en kısa örnek de COPY talimatının bu rolünü gösterir: Go kaynak dosyası build adlı aşamada derlenir, son aşama ise FROM scratch ile boş başlar ve yalnızca COPY --from=build /bin/hello /bin/hello satırıyla derlenmiş ikiliyi alır. Derleyici, kaynak kod ve ara dosyalar son imaja hiç girmez. Bu desenin ayrıntıları ve aşama adlandırma Dockerfile yazma rehberinde anlatılıyor; burada önemli olan, aşamalar arası taşımanın ADD ile değil COPY ile yapılmasıdır.

Yerel kaynak kodunu, yapılandırma dosyalarını ve son imajda bulunması gereken her şeyi de COPY ile eklersiniz. Kopyalama yalnızca build context'indeki dosyaları görür; context'in sınırları ve .dockerignore ile dışlanan dosyalar build context yazısında ayrıntılı anlatılıyor. Belgeler önemli bir ayrıntıyı belirtir: .dockerignore ile dışladığınız Dockerfile ve .dockerignore builder'a yine gönderilir, ama ADD, COPY ya da bind mount ile imaja kopyalanamaz. Dosya sistemi context'i olmadan, yani yalnızca stdin'den bir Dockerfile vererek yapılan build'de ise COPY yerel dosyaya başvurduğunda dosya bulunamadı hatası alırsınız.

Named context kullanıyorsanız --from değeri bir context adı da olabilir. Belgelerdeki örnekte --build-context config=./configs/prod ile verilen dizindeki nginx.conf, COPY --from=config nginx.conf /etc/nginx/nginx.conf satırıyla imaja alınır. Böylece aynı Dockerfile ile ortam başına farklı yapılandırma dizinleri kullanabilirsiniz.

Kopyalamak yerine bind mount

Bir dosyayı yalnızca tek bir RUN adımında kullanmak için imaja kopyalamanız gerekmez. Belgeler, build context'ten geçici olarak dosya eklemeniz gereken çoğu durumda COPY yerine bind mount kullanılabileceğini söyler. pip install için requirements.txt dosyasının bağlanması şöyle yazılır:

dockerfile
RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
    pip install --requirement /tmp/requirements.txt

Önbellek optimizasyonu sayfası bind mount'un neden daha verimli olduğunu açıklar: COPY ile eklediğiniz dosyalar, son imajda kullanılmasalar bile BuildKit tarafından önbelleğe dahil edilir; bind mount ise önbelleğe gereksiz katman eklemez. Bağlanan dosyalar yalnızca o RUN talimatı süresince vardır, son imajda ve build önbelleğinde kalmaz; geriye yalnızca komutun ürettiği çıktı kalır. Dikkat edilecek kurallar şunlardır:

  • Bind mount varsayılan olarak salt okunurdur. Yazmanız gerekiyorsa rw seçeneğini eklersiniz; ama yazdıklarınız yine son imaja ya da önbelleğe geçmez, talimat bitince atılır.
  • Komutun çıktısı bağlama hedefinin dışına yazılmalıdır. Belgelerdeki Go örneğinde kaynak /build dizinine bağlanır, ikili dosya ise /app/hello konumuna yazılır.
  • Hedef dizin boş değilse içeriği bağlanan dosyalar tarafından gizlenir; talimat bitince özgün içerik geri gelir.
  • Dosyanın son imajda bulunması gerekiyorsa bind mount değil COPY ya da ADD kullanılır.
dockerfile
FROM golang:latest
WORKDIR /build
RUN --mount=type=bind,target=. go build -o /app/hello

Bağımlılık indirmesini hızlandırmak için kullanılan cache mount (--mount=type=cache) farklı bir araçtır: build'ler arasında kalıcı bir paket önbelleği tutar. Bu konu ve önbellek arka uçları build cache yazısında anlatılıyor.

ADD ne zaman doğru seçimdir?

ADD ne zaman doğru seçimdir?

Belgelere göre ADD en çok, build'in bir parçası olarak uzak bir yapıtı indirmeniz gerektiğinde işe yarar. wget ve tar ile elle indirmeye göre daha kesin bir build önbelleği sağlar, uzak kaynaklar için yerleşik sağlama toplamı doğrulaması sunar ve Git adreslerinden dal, etiket ve alt dizin ayrıştırabilir. Belgelerdeki .NET örneğinden kısaltılmış şu yapı, arşivi ayrı bir aşamada indirip doğrular ve yalnızca açılmış çalışma zamanını son aşamaya taşır:

dockerfile
# syntax=docker/dockerfile:1
FROM scratch AS src
ARG DOTNET_VERSION=8.0.0-preview.6.23329.7
ADD --checksum=sha256:270d731bd08040c6a3228115de1f74b91cf441c584139ff8f8f6503447cebdbb \
    https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz /dotnet.tar.gz

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8 AS installer
RUN --mount=from=src,target=/src <<EOF
mkdir -p /dotnet
tar -oxzf /src/dotnet.tar.gz -C /dotnet
EOF

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8
COPY --from=installer /dotnet /usr/share/dotnet
RUN ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

--checksum ile verdiğiniz SHA256 değeri indirilen dosyayla eşleşmezse build başarısız olur. Build politikaları sayfası bu doğrulamanın sınırını da belirtir: sağlama toplamı içeriğin beklediğiniz içerik olduğunu kanıtlar, ama nereden geldiğini ya da nasıl üretildiğini söylemez ve yalnızca ADD için çalışır. Tüm build girdilerine kural koymak isterseniz deneysel durumdaki build politikalarına bakabilirsiniz; bu politikalar örneğin uzak yapıtların HTTPS kullanmasını ve bir sağlama toplamı içermesini zorunlu kılabilir.

ADD Git depolarını da çekebilir. Özel bir depoyu SSH ile almak için Dockerfile'a ADD git@github.com:me/myprivaterepo.git /src/ yazar, build'e --ssh default verirsiniz. HTTPS adresli özel depolarda GIT_AUTH_TOKEN sırrı aynı şekilde ADD için de geçerlidir. COPY ve ADD isteklerinde belirli bir sunucu için kimlik eklemek isterseniz HTTP_AUTH_TOKEN_<host> ya da HTTP_AUTH_HEADER_<host> sırlarını tanımlarsınız. Bu mekanizmalar build secrets yazısında ayrıntılı anlatılıyor.

dockerfile
# syntax=docker/dockerfile:1
FROM alpine
ADD git@github.com:me/myprivaterepo.git /src/

Önbellek kopyalanan dosyaları nasıl karşılaştırır?

Önbellek geçersizleştirme kuralları ADD ve COPY için özeldir. Builder bu talimatlarda ve bind mount kullanan RUN talimatlarında dosya metaverisinden bir önbellek sağlama toplamı hesaplar; ilgili dosyalardan herhangi birinin metaverisi değiştiyse önbellek geçersiz olur. Dosyanın değiştirilme zamanı (mtime) bu hesaba katılmaz: yalnızca mtime değiştiyse önbellek bozulmaz. Diğer talimatlarda ise container içindeki dosyalara bakılmaz, yalnızca komut metni karşılaştırılır.

Bir talimatın önbelleği bozulduğunda sonraki bütün talimatlar yeniden çalışır. Bu yüzden COPY . . gibi geniş bir kopyalamayı bağımlılık kurulumundan önce yazarsanız, herhangi bir kaynak dosyasındaki küçük bir değişiklik bütün bağımlılıkların yeniden kurulmasına yol açar. Belgelerdeki önerilen düzen, önce paket yönetimi dosyalarını kopyalamak, bağımlılıkları kurmak ve en son proje dosyalarını kopyalamaktır:

dockerfile
# syntax=docker/dockerfile:1
FROM node
WORKDIR /app
COPY package.json yarn.lock .
RUN npm install
COPY . .
RUN npm build

Sahiplik, izinler ve here-document

Kopyalanan dosyaların sahipliğini COPY --chown ile ayarlarsınız. Compose Watch belgesi bu bayrağı belirli bir senaryo için önerir: Watch'ın dosyaları güncelleyebilmesi için container'daki USER hedef yola yazabilmelidir ve ilk içeriği COPY ile kopyalarken --chown vermek dosyaların bu kullanıcıya ait olmasını sağlar. Çalıştırma izni gereken betikler için belgelerdeki çoklu süreç örneklerinde COPY --chmod=755 kullanılır. Sürüm notlarına göre Engine 29.6.0, COPY --chmod izinleri dahil açıkça verilen dosya kiplerinin daemon umask'ı tarafından filtrelenmesine yol açan bir hatayı düzeltmiştir.

Kısa bir dosyayı context'te tutmak istemiyorsanız COPY here-document da kabul eder. Belgelerdeki yeniden başlatma örneği bir başlangıç betiğini COPY --chmod=755 <<"EOF" /start.sh biçiminde doğrudan Dockerfile'a yazar; çok aşamalı build örneği de bir Go kaynak dosyasını aynı yolla oluşturur.

Aşağıdaki Compose dosyası bu teknikleri bir arada kullanır: bağımlılık dosyaları kopyalanmadan bind mount ile bağlanır, uygulama kodu yetkisiz kullanıcıya ait olacak şekilde kopyalanır. Docker CLI 29.8.1, Docker Compose v5.5.1 ile docker compose config --quiet çıkış kodu 0 verdi. Satır içi Dockerfile dockerfile_inline ile yazılmıştır; bu alan dockerfile ile birlikte kullanılamaz.

yaml
services:
  app:
    build:
      context: .
      dockerfile_inline: |
        # syntax=docker/dockerfile:1
        FROM node:22
        RUN useradd -ms /bin/sh -u 1001 app
        WORKDIR /app
        RUN --mount=type=bind,source=package.json,target=package.json \
            --mount=type=bind,source=package-lock.json,target=package-lock.json \
            npm ci --omit=dev
        COPY --chown=app:app . /app
        USER app
        CMD ["node", "server.js"]

Küçük bir örnek: Python servisini adım adım okumak

Yukarıdaki kuralları tek bir Dockerfile üzerinde birleştirelim. Aşağıdaki dosya bir Python web servisini sunucuda çalıştırmak için yazılmıştır ve her satırı belgelerdeki bir öneriye dayanır.

dockerfile
# syntax=docker/dockerfile:1
FROM python:3.12-slim
RUN groupadd -r app && useradd --no-log-init -r -g app app
WORKDIR /app
RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
    pip install --requirement /tmp/requirements.txt
COPY --chown=app:app . /app
USER app
CMD ["python", "main.py"]

Kullanıcı ve grup, en iyi uygulamalar sayfasındaki useradd --no-log-init örneğine göre oluşturulur. Bağımlılık listesi imaja kopyalanmaz, yalnızca kurulum adımında bind mount ile okunur; bu yüzden requirements.txt son imajda yer almaz. Bu dosyanın içeriği değişirse bind mount kullanan RUN adımının önbelleği bozulur ve bağımlılıklar yeniden kurulur; yalnızca uygulama kodu değiştiğinde ise önbellek COPY satırına kadar geçerli kalır ve sadece son kopyalama yeniden yapılır. Kod --chown ile yetkisiz kullanıcıya ait olarak kopyalanır ve USER satırından sonra servis root olmadan çalışır. Context'teki .git, sanal ortam dizinleri ve log dosyaları gibi gereksiz içerik ise .dockerignore ile dışarıda bırakılmalıdır; aksi halde COPY . /app onları da imaja taşır.

ONBUILD içinde ADD ve COPY

ONBUILD talimatı, imajınızdan FROM ile türetilen alt imajlarda, alt Dockerfile'daki her şeyden önce çalışır. Belgeler ONBUILD içine ADD ya da COPY koyarken dikkatli olmanızı söyler: yeni build'in context'inde eklenecek kaynak yoksa imaj tamamen başarısız olur. Bu riski azaltmak için ONBUILD içeren imajlara ruby:2.0-onbuild gibi ayrı bir etiket verilmesi önerilir; böylece Dockerfile yazan kişi bu davranışı bilinçli olarak seçer.

Sık yapılan hatalar

Sık yapılan hatalar: Yerel dosya kopyalamak için alışkanlıkla ADD kullanmak, Uzak bir dosyayı ADD ile sağlama toplamı vermeden indirmek, Yalnızca kurulum sırasında gereken...
  • Yerel dosya kopyalamak için alışkanlıkla ADD kullanmak: ek özelliğe ihtiyaç yoksa COPY niyeti daha açık anlatır.
  • Uzak bir dosyayı ADD ile sağlama toplamı vermeden indirmek: içerik değişirse fark etmezsiniz.
  • Yalnızca kurulum sırasında gereken dosyaları COPY ile ekleyip sonra silmek: dosya önceki katmanda kalır; bind mount daha temiz bir çözümdür.
  • Bind mount hedefinin içine çıktı yazmak: mount salt okunurdur ve yazılanlar korunmaz.
  • COPY . . satırını bağımlılık kurulumundan önce yazmak: her kod değişikliği tüm bağımlılıkları yeniden kurar.
  • .dockerignore ile dışlanan bir dosyayı kopyalamaya çalışmak.
  • Yetkisiz kullanıcıyla çalışan bir imajda kopyalanan dosyaların root'a ait kalması: --chown kullanın.

Sık Sorulan Sorular

ADD tar dosyasını her zaman açar mı?

Belgeler otomatik açmayı build context'ten eklenen tar dosyaları için tanımlar. Uzak bir arşivi indirip kendiniz açmak istiyorsanız yukarıdaki .NET örneğinde olduğu gibi indirmeyi ayrı bir aşamada yapıp tar ile açabilirsiniz.

COPY --from ile başka bir imajdan dosya alabilir miyim?

Evet. Yerel bir imaj adı, bir etiket ya da registry'deki bir etiket verebilirsiniz; Docker istemcisi gerekirse imajı çeker ve dosyayı oradan kopyalar.

Dosyanın sadece tarihi değişti, önbellek bozulur mu?

Hayır. COPY ve ADD için sağlama toplamı dosya metaverisinden hesaplanır ama mtime hesaba katılmaz; yalnızca değiştirilme zamanı farklıysa önbellek geçerliliğini korur.

Bind mount ile COPY arasında nasıl seçim yaparım?

Dosya son imajda bulunacaksa COPY, yalnızca tek bir RUN adımında okunacaksa bind mount kullanın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · Build context ve .dockerignore · CMD ve ENTRYPOINT farkı · Docker build secrets · Build cache backend'leri · Dockerfile yazma rehberi