Warunkowe KOPIUJ / DODAJ w Dockerfile?

103

Wewnątrz moich Dockerfiles chciałbym SKOPIOWAĆ plik do mojego obrazu, jeśli istnieje, plik Require.txt dla pip wydaje się być dobrym kandydatem, ale jak można to osiągnąć?

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

lub

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
źródło
Proszę zobaczyć tutaj: docs.docker.com/reference/builder
Tuan
4
@Tuan - Co konkretnie pod tym linkiem pomaga to zrobić?
ToolmakerSteve

Odpowiedzi:

24

Nie jest to obecnie obsługiwane (jak podejrzewam, że prowadziłoby to do niemożliwego do odtworzenia obrazu, ponieważ ten sam plik Dockerfile skopiowałby plik lub nie, w zależności od jego istnienia).

Jest to nadal wymagane w numerze 13045 , przy użyciu symboli wieloznacznych: „ COPY foo/* bar/" not work if no file in foo” (maj 2015).
Na razie nie zostanie zaimplementowany (lipiec 2015 r.) W Dockerze, ale inne narzędzie do kompilacji, takie jak bocker, może to obsługiwać.

VonC
źródło
33
dobra odpowiedź, ale logika dockera, IMO, jest błędna. jeśli uruchomisz ten sam plik dockerfile z innym kontekstem kompilacji, otrzymasz inny obraz. tego można się było spodziewać. użycie tego samego kontekstu kompilacji da ten sam obraz. a jeśli wstawisz warunkowe instrukcje COPY / ADD w tym samym kontekście kompilacji, otrzymasz ten sam obraz. więc to się sprawdza. to tylko moje 2 centy.
nathan g
Docker dotyczy niezmiennej infrastruktury. Twoje środowiska deweloperskie, inscenizacyjne i produkcyjne powinny być w 99,99% jak najbardziej zbliżone, jeśli nie identyczne. Użyj zmiennych środowiskowych.
Andrew McLagan,
3
@AndrewMcLagan co jeśli, na przykład, środowisko front-endowe devdziała z serwerem deweloperskim webpack , a jego odpowiednik proddziała z /distfolderem statycznym? Dzieje się tak w większości dzisiejszych konfiguracji front-end i oczywiście devi prodnie może być tutaj to samo. Jak więc sobie z tym poradzić?
Jivan
Nie używam Dockera do tworzenia interfejsów dla węzłów. Normalny localhost w pakiecie webpack: 3000 itd. Mimo że nadal uruchamiaj lokalne środowisko Docker dev, aby Twój interfejs Node / React / Angular komunikował się z wszystkim, co działa w normalnym środowisku kontenera Docker. Np. API, redis, MySQL, mongo, elastyczne wyszukiwanie i wszelkie inne mikro usługi. Możesz ..można .. uruchomić środowisko programistyczne webpack w kontenerze. Ale czuję, że to za daleko ...
AndrewMcLagan
@Jivan Co powiesz na użycie obrazu onbuild do zdefiniowania typowych instrukcji, a następnie zbudowanie określonych obrazów dla deweloperów i produktów. Wydaje się, że repozytorium Docker Hub Node zawiera obrazy onbuild dla każdej wersji węzła: hub.docker.com/_/node . A może mógłbyś skręcić własną.
david_i_smith
84

Oto proste obejście:

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

Upewnij się, że fooistnieje, ponieważ COPYwymaga co najmniej jednego prawidłowego źródła.

Jeśli file-which-may-existjest obecny, zostanie również skopiowany.

UWAGA: Powinieneś uważać, aby twój symbol wieloznaczny nie odbierał innych plików, których nie zamierzasz kopiować. Aby być bardziej ostrożnym, możesz użyć file-which-may-exist?zamiast tego ( ?dopasowuje tylko jeden znak).

Lub jeszcze lepiej, użyj takiej klasy znaków, aby upewnić się, że dopasowany zostanie tylko jeden plik:

COPY foo file-which-may-exis[t] /target
jdhildeb
źródło
1
Czy możesz zrobić to samo z folderem?
Benjamin Toueg,
1
@BenjaminToueg: Tak, zgodnie z dokumentami możesz kopiować zarówno pliki, jak i foldery.
jdhildeb
2
To działa świetnie. W przypadku plików z wieloma miejscami docelowymi skopiowałem do katalogu tymczasowego, a następnie przeniosłem je tam, gdzie było to potrzebne. 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(gdzie współdzielona biblioteka jest nieznaną jednostką.)
Adam K Dean
Podczas kopiowania wielu istniejących plików miejscem docelowym musi być katalog. Jak to działa, gdy istnieje zarówno foo, jak i plik-który-może-istnieć *?
melchoir55,
1
Zatem odpowiedź brzmi „upewnij się, że istnieje plik”, a następnie demonstracja, jak używać operatora COPY? Nie rozumiem, jak to się ma do pierwotnego pytania.
derrend,
27

Jak stwierdzono w tym komentarzu , odpowiedź Santhosha Hirekerura nadal kopiuje plik, aby zarchiwizować prawdziwą kopię warunkową, możesz użyć tej metody.

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

Te ONBUILDinstrukcje gwarantuje, że plik jest kopiowany tylko wtedy, gdy „oddział” został wybrany przez BUILD_ENV. Ustaw tę zmienną za pomocą małego skryptu przed wywołaniemdocker build

Siyu
źródło
2
Podoba mi się ta odpowiedź, ponieważ otworzyła mi oczy nie tylko na ONBUILD, który jest bardzo poręczny, ale wydaje się również, że najłatwiej jest go zintegrować z innymi przekazanymi zmiennymi, np. Jeśli chcesz ustawić tag w oparciu o BUILD_ENV lub zapisać jakiś stan w ENV.
DeusXMachina
Właśnie wypróbowałem coś takiego i otrzymałem: Odpowiedź błędu od demona: Dockerfile parse Error Line 52: nieprawidłowa nazwa etapu kompilacji: „site_builder _ $ {host_env}”, nazwa nie może zaczynać się od liczby ani zawierać symboli
paulecoyote
9

Obejdź rozwiązanie

Miałem wymóg skopiowania FOLDERU na serwer w oparciu o zmienne ENV. Zrobiłem pusty obraz serwera. utworzył wymaganą strukturę folderów wdrożeniowych w folderze lokalnym. następnie dodano poniższy wiersz do DockerFile, skopiuj folder do kontenera. W ostatnim wierszu dodano punkt wejścia do uruchomienia pliku init.sh przed dockerem uruchomieniem serwera.

#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"]

Następnie utwórz plik custom-init.sh lokalnie ze skryptem podobnym do poniższego

#!/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;

W pliku docker-compose poniżej linii.

środowisko: - BUILD_EVN = TEST

Te zmiany kopiują folder do kontenera podczas kompilacji platformy Docker. kiedy wykonujemy docker-compose up , skopiuj lub wdrożysz właściwy folder na serwerze przed jego uruchomieniem.

Santhosh Hirekerur
źródło
8
Ale obrazy Dockera są warstwowe. ADD skopiowałoby je do obrazu niezależnie od wspomnianej instrukcji if ...
MyUserInStackOverflow
@MyUserInStackOverflow - myślę, że idea tego „obejścia” polega na tym, że zarówno install, jak i install_test są kopiowane do obrazu, ale po uruchomieniu obrazu tylko jeden z tych folderów jest kopiowany do ostatecznej lokalizacji. Jeśli jest w porządku, że oba są gdzieś na obrazie, może to być rozsądna technika.
ToolmakerSteve
5

Skopiuj wszystkie pliki do jednorazowego katalogu, ręcznie wybierz ten, który chcesz, a resztę odrzuć.

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

Możesz osiągnąć coś podobnego za pomocą etapów kompilacji, które polegają na tym samym rozwiązaniu, używając cpdo warunkowego kopiowania. Korzystając z etapu kompilacji, ostateczny obraz nie będzie zawierał całej zawartości początkowej 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
źródło
Chociaż technicznie rozwiązuje to problem, nie zmniejsza to rozmiaru obrazu. Jeśli próbujesz warunkowo skopiować coś dużego (jak model głębokiej sieci), nadal zawyżasz rozmiar obrazu, ze względu na sposób działania nakładki fs.
DeusXMachina
@DeusXMachina, jakiej wersji dockera używasz? Dokumenty zaprzeczają temu, co mówisz docs.docker.com/develop/develop-images/multistage-build/… . Warstwy nie powinny być zachowywane z nie ostatecznego etapu budowy.
cdosborn
@cdosburn - zauważyłem to 18.09.2018. Mówiłem głównie o pierwszym przykładzie, kompilacje etapowe powinny unikać tego problemu. I myślę, że teraz każdy etap FROM się zagęszcza, ale po raz drugi odgaduję moje wspomnienie. Z niektórymi rzeczami będę musiał poeksperymentować.
DeusXMachina
@DeusXMachina, tylko drugie rozwiązanie zmniejsza rozmiar obrazu.
cdosborn
to fajne obejście w moim przypadku. Kopiuję cachei w zależności od tego, co to cache wybieram, co robić w plikach skryptów!
Paschalis
1

Wypróbowałem inne pomysły, ale żaden nie spełnił naszych wymagań. Pomysł polega na utworzeniu podstawowego obrazu nginx dla podrzędnych statycznych aplikacji internetowych. Ze względów bezpieczeństwa, optymalizacji i standaryzacji obraz podstawowy musi umożliwiać RUNpolecenia dotyczące katalogów dodanych przez obrazy potomne. Obraz podstawowy nie kontroluje, które katalogi są dodawane przez obrazy podrzędne. Zakłada się, że obrazy potomne będą COPYznajdowały się gdzieś pod COMMON_DEST_ROOT.

To podejście to hack, ale idea jest taka, że ​​obraz podstawowy będzie obsługiwał COPYinstrukcje dla katalogów od 1 do N dodanych przez obraz potomny. ARG PLACEHOLDER_FILEi ENV UNPROVIDED_DESTsą wykorzystywane w celu zaspokojenia <src>i <dest>wymagania dla każdej COPYnie potrzeba instrukcji.

#
# 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

To rozwiązanie ma oczywiste wady, takie jak fikcyjna PLACEHOLDER_FILEi zakodowana na stałe liczba obsługiwanych instrukcji COPY. Nie ma również sposobu na pozbycie się zmiennych ENV, które są używane w instrukcji COPY.

brianNotBob
źródło