COPY / ADD bersyarat di Dockerfile?

103

Di dalam Dockerfiles saya, saya ingin MENYALIN file ke dalam image saya jika ada, file requirement.txt untuk pip sepertinya merupakan kandidat yang baik, tetapi bagaimana ini bisa dicapai?

COPY (requirements.txt if test -e requirements.txt; fi) /destination
...
RUN  if test -e requirements.txt; then pip install -r requirements.txt; fi

atau

if test -e requirements.txt; then
    COPY requiements.txt /destination;
fi
RUN  if test -e requirements.txt; then pip install -r requirements.txt; fi
derrend
sumber
Silakan lihat di sini: docs.docker.com/reference/builder
Tuan
4
@Tuan - Apa secara spesifik pada tautan itu yang membantu melakukan ini?
ToolmakerSteve

Jawaban:

24

Ini saat ini tidak didukung (karena saya menduga itu akan mengarah ke gambar yang tidak dapat direproduksi, karena Dockerfile yang sama akan menyalin atau tidak file, tergantung pada keberadaannya).

Ini masih diminta, di edisi 13045 , menggunakan karakter pengganti: " COPY foo/* bar/" not work if no file in foo" (Mei 2015).
Ini tidak akan diterapkan untuk saat ini (Juli 2015) di Docker, tetapi alat build lain seperti bocker dapat mendukung ini.

VonC
sumber
32
jawaban yang bagus, tapi logika buruh pelabuhan, IMO, cacat. jika Anda menjalankan dockerfile yang sama dengan konteks build yang berbeda, Anda akan mendapatkan gambar yang berbeda. itulah yang diharapkan. menggunakan konteks build yang sama akan memberikan gambar yang sama. dan jika Anda memasukkan instruksi COPY / ADD bersyarat pada konteks build yang sama, Anda akan mendapatkan gambar yang sama. jadi itu check out. itu hanya 2 sen saya.
nathan g
Docker adalah tentang infrastruktur yang tidak dapat diubah. Dev, staging, dan prod lingkungan Anda harus 99,99% sedekat mungkin jika tidak identik. Gunakan variabel lingkungan.
AndrewMcLagan
3
@AndrewMcLagan bagaimana jika, misalnya, lingkungan front-end devberjalan dengan server dev webpack, dan prodlingkungan yang setara bekerja dengan /distfolder statis? Ini adalah kasus di sebagian besar penyiapan front-end hari ini, dan jelas devdan prodtidak bisa sama di sini. Jadi bagaimana mengatasinya?
Jivan
Saya tidak menggunakan buruh pelabuhan untuk mengembangkan ujung depan node saya. Localhost webpack normal: 3000 dll ... Meskipun masih mem-boot lingkungan dev docker lokal Anda sehingga ujung depan node / react / angular berkomunikasi dengan apa pun yang berjalan di lingkungan container docker normal Anda. Misalnya API, redis, MySQL, mongo, elastic search, dan layanan mikro lainnya. Anda .. dapat .., menjalankan lingkungan dev webpack dalam sebuah wadah. Tapi saya merasa itu terlalu jauh ...
AndrewMcLagan
@Jivan Bagaimana kalau menggunakan gambar onbuild untuk menentukan instruksi umum dan kemudian membangun gambar khusus untuk dev dan prod. Repo Docker Hub Node tampaknya berisi image onbuild untuk setiap versi Node: hub.docker.com/_/node . Atau mungkin Anda bisa menggulung sendiri.
david_i_smith
83

Berikut ini solusi sederhana:

COPY foo file-which-may-exist* /target

Pastikan fooada, karena COPYmembutuhkan setidaknya satu sumber yang valid.

Jika file-which-may-existada, itu juga akan disalin.

CATATAN: Anda harus berhati-hati untuk memastikan bahwa wildcard Anda tidak mengambil file lain yang tidak ingin Anda salin. Untuk lebih berhati-hati, Anda bisa menggunakan file-which-may-exist?sebagai gantinya ( ?hanya cocok dengan satu karakter).

Atau bahkan lebih baik, gunakan kelas karakter seperti ini untuk memastikan bahwa hanya satu file yang dapat dicocokkan:

COPY foo file-which-may-exis[t] /target
jdhildeb.dll
sumber
1
Bisakah Anda melakukan hal yang sama dengan folder?
Benjamin Toueg
1
@BenjaminToueg: Ya, menurut dokumen Anda dapat menyalin file dan juga folder.
jdhildeb
2
Ini bekerja dengan baik. Untuk file dengan banyak tujuan, saya menyalin ke direktori sementara dan kemudian memindahkannya ke tempat yang diperlukan. COPY --from=docker /usr/bin/docker /usr/lib/libltdl.so* /tmp/docker/ RUN mv /tmp/docker/docker /usr/bin/docker RUN mv /tmp/docker/libltdl.so.7 /usr/lib/libltdl.so.7 || true(di mana perpustakaan bersama adalah entitas yang tidak diketahui.)
Adam K Dean
Saat menyalin beberapa file yang masih ada, tujuannya harus berupa direktori. Bagaimana cara kerjanya jika foo dan file Anda-yang-mungkin-ada * ada?
melchoir55
1
Jadi jawabannya adalah 'pastikan ada file' dan kemudian demonstrasi tentang cara menggunakan operator COPY? Saya gagal untuk melihat bagaimana ini berhubungan dengan pertanyaan awal.
derrend
27

Seperti yang dinyatakan oleh komentar ini , jawaban Santhosh Hirekerur masih menyalin file, untuk mengarsipkan salinan bersyarat yang sebenarnya, Anda dapat menggunakan metode ini.

ARG BUILD_ENV=copy

FROM alpine as build_copy
ONBUILD COPY file /file

FROM alpine as build_no_copy
ONBUILD RUN echo "I don't copy"

FROM build_${BUILD_ENV}
# other stuff

The ONBUILDpetunjuk memastikan bahwa file tersebut hanya disalin jika "cabang" dipilih oleh BUILD_ENV. Setel var ini menggunakan skrip kecil sebelum memanggildocker build

Siyu
sumber
2
Saya suka jawaban ini karena tidak hanya membuka mata saya untuk ONBUILD, yang sangat berguna, tetapi juga tampaknya paling mudah untuk diintegrasikan dengan variabel lain yang diteruskan, misalnya jika Anda ingin menyetel tag berdasarkan BUILD_ENV, atau menyimpan beberapa status di ENV.
DeusXMachina
Saya baru saja mencoba sesuatu seperti itu dan mendapatkan: Respons kesalahan dari daemon: Dockerfile parse error line 52: nama tidak valid untuk tahap pembuatan: "site_builder _ $ {host_env}", nama tidak dapat dimulai dengan angka atau berisi simbol
paulecoyote
9

Bekerja di sekitar Solution

Saya memiliki persyaratan untuk menyalin FOLDER ke server berdasarkan Variabel ENV. Saya mengambil gambar server kosong. membuat struktur folder penyebaran yang diperlukan di dalam folder lokal. lalu tambahkan baris di bawah ini ke DockerFile, salin folder ke container. Aku n baris terakhir ditambahkan entry point untuk mengeksekusi file.sh init sebelum buruh pelabuhan mulai server.

#below lines added to integrate testing framework
RUN mkdir /mnt/conf_folder
ADD install /mnt/conf_folder/install
ADD install_test /mnt/conf_folder/install_test
ADD custom-init.sh /usr/local/bin/custom-init.sh
ENTRYPOINT ["/usr/local/bin/custom-init.sh"]

Kemudian buat file custom-init.sh di lokal dengan script seperti di bawah ini

#!/bin/bash
if [ "${BUILD_EVN}" = "TEST" ]; then
    cp -avr /mnt/conf_folder/install_test/* /mnt/wso2das-3.1.0/
else
    cp -avr /mnt/conf_folder/install/* /mnt/wso2das-3.1.0/
fi;

Di buruh pelabuhan-menulis file bawah baris.

lingkungan: - BUILD_EVN = TEST

Perubahan ini menyalin folder ke penampung selama pembuatan buruh pelabuhan. ketika kita menjalankan docker-compose , salin atau gunakan folder yang sebenarnya diperlukan ke server sebelum server dimulai.

Santhosh Hirekerur
sumber
8
Tapi gambar buruh pelabuhan berlapis. ADD akan menyalin ini ke gambar terlepas dari pernyataan if yang Anda sebutkan ...
MyUserInStackOverflow
@MyUserInStackOverflow - Menurut saya ide dari "solusi" ini adalah bahwa install dan install_test disalin ke dalam gambar, tetapi ketika gambar dijalankan, hanya satu dari folder itu yang disalin ke lokasi akhir. Jika tidak masalah bahwa keduanya ada di suatu tempat dalam gambar, ini bisa menjadi teknik yang masuk akal.
ToolmakerSteve
4

Salin semua file ke direktori sekali pakai, pilih yang Anda inginkan, buang sisanya.

COPY . /throwaway
RUN cp /throwaway/requirements.txt . || echo 'requirements.txt does not exist'
RUN rm -rf /throwaway

Anda dapat mencapai sesuatu yang serupa menggunakan tahapan build, yang bergantung pada solusi yang sama, menggunakan cpuntuk menyalin secara bersyarat. Dengan menggunakan tahap pembuatan, gambar akhir Anda tidak akan menyertakan semua konten dari awal COPY.

FROM alpine as copy_stage
COPY . .
RUN mkdir /dir_for_maybe_requirements_file
RUN cp requirements.txt /dir_for_maybe_requirements_file &>- || true

FROM alpine
# Must copy a file which exists, so copy a directory with maybe one file
COPY --from=copy_stage /dir_for_maybe_requirements_file /
RUN cp /dir_for_maybe_requirements_file/* . &>- || true
CMD sh
cdosborn.dll
sumber
Meskipun secara teknis ini menyelesaikan masalah, ini tidak mengurangi ukuran gambar. Jika Anda mencoba untuk menyalin secara bersyarat sesuatu yang besar (seperti model jaringan dalam) Anda masih memperbesar ukuran gambar, karena cara kerja overlay fs.
DeusXMachina
@DeusXMachina, versi buruh pelabuhan apa yang Anda gunakan? Dokumen tersebut bertentangan dengan apa yang Anda katakan docs.docker.com/develop/develop-images/multistage-build/… . Lapisan tersebut tidak boleh dipertahankan dari tahap pembuatan non-final.
cdosborn
@cdosburn - Saya telah mengamati ini pada 18.09. Saya berbicara sebagian besar tentang contoh pertama, pembangunan bertahap harus menghindari masalah itu. Dan saya pikir setiap FROM tahap memadat sekarang, tetapi Anda membuat saya menebak-nebak ingatan saya. Saya harus bereksperimen dengan beberapa hal.
DeusXMachina
@DeusXMachina, hanya solusi kedua yang mengurangi ukuran gambar.
cdosborn
itu solusi yang bagus untuk kasus saya. Saya menyalin cachedan tergantung apa itu cache saya memilih apa yang harus dilakukan dalam file skrip!
Paschalis
1

Mencoba ide lain, tetapi tidak ada yang memenuhi persyaratan kami. Idenya adalah membuat gambar nginx dasar untuk aplikasi web statis anak. Untuk alasan keamanan, pengoptimalan, dan standarisasi, gambar dasar harus dapat menjalankan RUNperintah pada direktori yang ditambahkan oleh gambar anak. Gambar dasar tidak mengontrol direktori mana yang ditambahkan oleh gambar anak. Asumsinya adalah gambar anak akan COPYsumber daya di suatu tempat di bawah COMMON_DEST_ROOT.

Pendekatan ini adalah hack, tetapi idenya adalah gambar dasar akan mendukung COPYinstruksi untuk direktori 1 ke N yang ditambahkan oleh gambar anak. ARG PLACEHOLDER_FILEdan ENV UNPROVIDED_DESTdigunakan untuk memenuhi <src>dan <dest>persyaratan untuk setiap COPYinstruksi yang tidak diperlukan.

#
# base-image:01
#
FROM nginx:1.17.3-alpine
ENV UNPROVIDED_DEST=/unprovided
ENV COMMON_DEST_ROOT=/usr/share/nginx/html
ONBUILD ARG PLACEHOLDER_FILE
ONBUILD ARG SRC_1
ONBUILD ARG DEST_1
ONBUILD ARG SRC_2
ONBUILD ARG DEST_2
ONBUILD ENV SRC_1=${SRC_1:-PLACEHOLDER_FILE}
ONBUILD ENV DEST_1=${DEST_1:-${UNPROVIDED_DEST}}
ONBUILD ENV SRC_2=${SRC_2:-PLACEHOLDER_FILE}
ONBUILD ENV DEST_2=${DEST_2:-${UNPROVIDED_DEST}}

ONBUILD COPY ${SRC_1} ${DEST_1}
ONBUILD COPY ${SRC_2} ${DEST_2}

ONBUILD RUN sh -x \
    #
    # perform operations on COMMON_DEST_ROOT
    #
    && chown -R limited:limited ${COMMON_DEST_ROOT} \
    #
    # remove the unprovided dest
    #
    && rm -rf ${UNPROVIDED_DEST}

#
# child image
#
ARG PLACEHOLDER_FILE=dummy_placeholder.txt
ARG SRC_1=app/html
ARG DEST_1=/usr/share/nginx/html/myapp
FROM base-image:01

Solusi ini memiliki kekurangan yang jelas seperti nomor dummy PLACEHOLDER_FILEdan kode keras dari instruksi COPY yang didukung. Juga tidak ada cara untuk menghilangkan variabel ENV yang digunakan dalam instruksi COPY.

brianNotBob
sumber