Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Zanim nowa funkcjonalność trafi na produkcję, w erze złożonych orkiestratorów i CI/CD musi przejść długą drogę od commita do testów i dostarczenia. Kiedyś wystarczyło wrzucić nowe pliki przez FTP (nikt już tak nie robi, prawda?), a proces „deployowania” zajmował sekundy. Teraz trzeba stworzyć merge request i czekać dość długo, aż nowość dotrze do użytkowników.

Częścią tej drogi jest budowanie obrazu Docker. Czasami budowa trwa minuty, czasami dziesiątki minut, co trudno nazwać normalnym. W tym artykule weźmiemy proste aplikacje, które spakujemy w obraz, zastosujemy kilka metod przyspieszających budowę i omówimy szczegóły tych metod.

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Mamy całkiem niezłe doświadczenie w tworzeniu i wsparciu stron internetowych mediów: TASS, The Bell, "Nowa gazeta", Republic… Niedawno wzbogaciliśmy nasze portfolio, uruchamiając stronę produkcyjną Reminder. I podczas gdy szybko dopracowywaliśmy nowe funkcjonalności i naprawialiśmy stare błędy, wolny proces wdrażania stał się dużym problemem.

Deploy robimy na GitLabie. Budujemy obrazy, przesyłamy do GitLab Registry i wprowadzamy na produkcję. Najdłuższe w tym procesie jest tworzenie obrazów. Dla przykładu: bez optymalizacji każda budowa backendu zajmowała 14 minut.

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

W końcu stało się jasne, że tak dalej nie można żyć, więc usiedliśmy, aby zrozumieć, dlaczego obrazy budują się tak długo. Ostatecznie udało się skrócić czas budowy do 30 sekund!

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

W tej artykule, aby nie wiązać się z otoczeniem Reminder’a, rozważymy przykład budowy pustej aplikacji na Angular. Zaczynamy tworzyć naszą aplikację:

ng n app

Dodajemy do niej PWA (jesteśmy przecież nowocześni):

ng add @angular/pwa --project app

Podczas pobierania miliona pakietów npm, przyjrzyjmy się, jak działa obraz dockerowy. Docker umożliwia pakowanie aplikacji i uruchamianie ich w izolowanym środowisku, które nazywa się kontenerem. Dzięki izolacji można jednocześnie uruchamiać wiele kontenerów na jednym serwerze. Kontenery są znacznie lżejsze od maszyn wirtualnych, ponieważ działają bezpośrednio na jądrze systemu. Żeby uruchomić kontener z naszą aplikacją, najpierw musimy stworzyć obraz, w którym zapakujemy wszystko, co potrzebne do działania naszej aplikacji. Obraz to zasadniczo odzwierciedlenie systemu plików. Na przykład, weźmy Dockerfile:

FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Dockerfile to zestaw instrukcji; wykonując każdą z nich, Docker będzie zapisywał zmiany w systemie plików i nakładał je na poprzednie. Każda komenda tworzy swój warstwę. A gotowy obraz to połączone razem warstwy.

Co ważne, każdy warstwa jest buforowana. Jeśli nic nie zmieniło się od ostatniego budowania, to zamiast wykonywać komendę, Docker weźmie już gotową warstwę. Ponieważ główny przyrost w szybkości budowania będzie wynikał z użycia pamięci podręcznej, w pomiarach szybkości budowania będziemy zwracać uwagę właśnie na budowanie obrazu z gotowym buforem. Zatem, krok po kroku:

  1. Usuwamy obrazy lokalnie, aby wcześniejsze uruchomienia nie wpływały na test.
    docker rmi $(docker images -q)
  2. Uruchamiamy budowę pierwszy raz.
    time docker build -t app .
  3. Zmiana pliku src/index.html — symulując pracę programisty.
  4. Uruchamiamy budowę drugi raz.
    time docker build -t app .

Jeśli środowisko do budowy obrazów jest poprawnie skonfigurowane (o czym niżej), to Docker podczas uruchamiania budowy już będzie miał na pokładzie wiele buforów. Naszym zadaniem jest nauczyć się używać pamięci podręcznej, tak aby budowa przebiegła maksymalnie szybko. Ponieważ zakładamy, że uruchomienie budowy bez pamięci podręcznej występuje tylko raz — pierwszy — więc możemy zignorować, jak wolne było to pierwsze uruchomienie. W testach ważne jest dla nas drugie uruchomienie budowy, kiedy bufory są już nagrzane i jesteśmy gotowi piec nasz placek. Niemniej jednak, niektóre wskazówki wpłyną również na pierwszą budowę.

Umieszczamy wcześniej opisany Dockerfile w folderze z projektem i uruchamiamy budowę. Wszystkie podane listingi są skrócone dla wygody czytania.

$ time docker build -t app .
Wysyłanie kontekstu budowy do demona Docker 409MB
Krok 1/5 : FROM node:12.16.2
Status: Pobieranie nowszego obrazu dla node:12.16.2
Krok 2/5 : WORKDIR /app
Krok 3/5 : COPY . .
Krok 4/5 : RUN npm ci
dodano 1357 paczek w 22.47s
Krok 5/5 : RUN npm run build --prod
Data: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Czas: 37581ms
Pomyślnie zbudowano c8c279335f46
Pomyślnie oznaczono app:latest

czas rzeczywisty 5m4.541s
czas użytkownika 0m0.000s
czas systemowy 0m0.000s

Zmiana zawartości src/index.html i uruchomienie drugi raz.

$ time docker build -t app .
Wysyłanie kontekstu budowy do demona Docker 409MB
Krok 1/5 : FROM node:12.16.2
Krok 2/5 : WORKDIR /app
 ---> Użycie pamięci podręcznej
Krok 3/5 : COPY . .
Krok 4/5 : RUN npm ci
dodano 1357 paczek w 22.47s
Krok 5/5 : RUN npm run build --prod
Data: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Czas: 37902ms
Pomyślnie zbudowano 79f335df92d3
Pomyślnie oznaczono app:latest

czas rzeczywisty 3m33.262s
czas użytkownika 0m0.000s
czas systemowy 0m0.000s

Aby sprawdzić, czy obraz się udał, wykonujemy komendę docker images:

REPOSITORY   TAG      IMAGE ID       CREATED              SIZE
app          latest   79f335df92d3   Około minuty temu   1.74GB

Przed zbudowaniem obrazu, Docker zbiera wszystkie pliki z aktualnego kontekstu i przesyła je do swojego demona. Przesyłanie kontekstu budowy do demona Docker 409 MB. Kontekst budowy podawany jest jako ostatni argument polecenia build. W naszym przypadku jest to bieżący katalog — ".", a Docker zaciąga wszystko, co jest w tym folderze. 409 MB to dużo, zastanówmy się, jak to poprawić.

Zmniejszamy kontekst

Aby zmniejszyć kontekst, są dwa warianty. Można przenieść wszystkie pliki potrzebne do budowy do osobnego folderu i wskazać kontekst dla Dockera dokładnie na ten folder. Może to nie zawsze być wygodne, dlatego istnieje możliwość określenia wyjątków: co nie powinno być dołączone do kontekstu. W tym celu umieścimy w projekcie plik .dockerignore i zaznaczymy, co nie jest potrzebne do budowy:

.git
/node_modules

i uruchomimy budowę jeszcze raz:

$ time docker build -t app .
Przesyłanie kontekstu budowy do demona Docker 607.2kB
Krok 1/5: FROM node:12.16.2
Krok 2/5: WORKDIR /app
 ---> Użycie pamięci podręcznej
Krok 3/5: COPY . .
Krok 4/5: RUN npm ci
dodano 1357 pakietów w 22.47s
Krok 5/5: RUN npm run build --prod
Data: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Czas: 37313ms
Sukces: zbudowano 4942f010792a
Pomyślnie otagowano app:latest

real 1m47.763s
user 0m0.000s
sys 0m0.000s

607.2 kB — znacznie lepiej niż 409 MB. Dodatkowo zmniejszyliśmy rozmiar obrazu z 1.74 do 1.38 GB:

REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app          latest   4942f010792a   3 minuty temu   1.38GB

Spróbujmy jeszcze bardziej zmniejszyć rozmiar obrazu.

Używamy Alpine

Kolejnym sposobem na zaoszczędzenie na rozmiarze obrazu jest użycie małego obrazu bazowego. Obraz bazowy to obraz, na podstawie którego tworzymy nasz obraz. Dolny warstwa jest określona w poleceniu Z FROM w Dockerfile. W naszym przypadku używamy obrazu na podstawie Ubuntu, na którym już znajduje się Node.js. A jego rozmiar to …

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minut temu 916MB

… prawie gigabajt. Znacznie można zmniejszyć rozmiar, korzystając z obrazu na podstawie Alpine Linux. Alpine to bardzo mały Linux. Obraz Docker dla Node.js na bazie Alpine waży tylko 88.5 MB. Dlatego zamieńmy nasz tłusty obraz:

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Musieliśmy zainstalować kilka rzeczy, które są niezbędne do budowy aplikacji. Tak, Angular nie buduje się bez Pythona ¯(°_o)\/¯

Ale dzięki temu rozmiar obrazu spadł o 150 MB:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   aa031edc315a   22 minuty temu   761MB

Idziemy dalej.

Budowa wieloetapowa

Nie wszystko, co jest w obrazie, jest nam potrzebne w produkcji.

$ docker run app ls -lah
total 576K
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 .
drwxr-xr-x 1 root root 4.0K Apr 16 20:00 ..
-rwxr-xr-x 1 root root 19 Apr 17 2020 .dockerignore
-rwxr-xr-x 1 root root 246 Apr 17 2020 .editorconfig
-rwxr-xr-x 1 root root 631 Apr 17 2020 .gitignore
-rwxr-xr-x 1 root root 181 Apr 17 2020 Dockerfile
-rwxr-xr-x 1 root root 1020 Apr 17 2020 README.md
-rwxr-xr-x 1 root root 3.6K Apr 17 2020 angular.json
-rwxr-xr-x 1 root root 429 Apr 17 2020 browserslist
drwxr-xr-x 3 root root 4.0K Apr 16 19:54 dist
drwxr-xr-x 3 root root 4.0K Apr 17 2020 e2e
-rwxr-xr-x 1 root root 1015 Apr 17 2020 karma.conf.js
-rwxr-xr-x 1 root root 620 Apr 17 2020 ngsw-config.json
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 node_modules
-rwxr-xr-x 1 root root 494.9K Apr 17 2020 package-lock.json
-rwxr-xr-x 1 root root 1.3K Apr 17 2020 package.json
drwxr-xr-x 5 root root 4.0K Apr 17 2020 src
-rwxr-xr-x 1 root root 210 Apr 17 2020 tsconfig.app.json
-rwxr-xr-x 1 root root 489 Apr 17 2020 tsconfig.json
-rwxr-xr-x 1 root root 270 Apr 17 2020 tsconfig.spec.json
-rwxr-xr-x 1 root root 1.9K Apr 17 2020 tslint.json

Dzięki docker run app ls -lah Uruchomiliśmy kontener na podstawie naszego obrazu app i wykonaliśmy w nim polecenie ls -lah, po czym kontener zakończył swoją pracę.

Na produkcji potrzebujemy tylko folderu dist. Pliki muszą być jakoś udostępnione na zewnątrz. Można uruchomić jakiś serwer HTTP na nodejs. Ale zrobimy to prościej. Zgadnijcie rosyjskie słowo, w którym są cztery litery „ы”. Dobrze! Ынжыныксы. Weźmiemy obraz z nginx, włożymy do niego folder dist i małą konfigurację:

server {
    listen 80 default_server;
    server_name localhost;
    charset utf-8;
    root /app/dist;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Wszystko to pomoże nam zbudować multi-stage build. Zmienimy nasz Dockerfile:

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Teraz mamy dwie instrukcje Z FROM w Dockerfile, każda z nich uruchamia swój etap budowy. Pierwszy nazwałem buildera, którą zadamy nieco później. Nazwa w naszym przypadku będzie taka sama, jak nazwa projektu:, a od ostatniego FROM będzie przygotowywany nasz ostateczny obraz. Ostatnim krokiem jest skopiowanie artefaktu naszej budowy z poprzedniego etapu do końcowego obrazu z nginx. Rozmiar obrazu znacznie się zmniejszył:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   2c6c5da07802   29 minut temu   36MB

Uruchommy kontener z naszym obrazem i upewnijmy się, że wszystko działa:

docker run -p8080:80 app

Opcją -p8080:80 przekazaliśmy port 8080 na naszej maszynie hosta do portu 80 wewnątrz kontenera, gdzie działa nginx. Otwieramy w przeglądarce http://localhost:8080/ i widzimy naszą aplikację. Wszystko działa!

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Zmniejszenie rozmiaru obrazu z 1.74 GB do 36 MB znacznie skraca czas dostarczania twojej aplikacji na produkcję. Ale wróćmy do czasu budowy.

$ time docker build -t app .
Wysyłanie kontekstu budowy do demona Docker 608.8kB
Krok 1/11 : FROM node:12.16.2-alpine3.11 as builder
Krok 2/11 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Używając pamięci podręcznej
Krok 3/11 : WORKDIR /app
 ---> Używając pamięci podręcznej
Krok 4/11 : COPY . .
Krok 5/11 : RUN npm ci
dodano 1357 pakietów w 47.338s
Krok 6/11 : RUN npm run build --prod
Data: 2020-04-16T21:16:03.899Z - Hash: fffa0fddaa3425c55dd3 - Czas: 39948ms
 ---> 27f1479221e4
Krok 7/11 : FROM nginx:stable-alpine
Krok 8/11 : WORKDIR /app
 ---> Używając pamięci podręcznej
Krok 9/11 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Używając pamięci podręcznej
Krok 10/11 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Używając pamięci podręcznej
Krok 11/11 : COPY --from=builder /app/dist/app .
Pomyślnie zbudowano d201471c91ad
Pomyślnie oznaczono app:latest

czas rzeczywisty 2m17.700s
czas użytkownika 0m0.000s
czas systemowy 0m0.000s

Zmiana kolejności warstw

Pierwsze trzy kroki zostały zachowane w pamięci podręcznej (wskaźnik Używając pamięci podręcznej). Na czwartym kroku kopiowane są wszystkie pliki projektu, a na piątym kroku instalowane są zależności RUN npm ci — całe 47.338s. Po co za każdym razem na nowo instalować zależności, skoro zmieniają się one bardzo rzadko? Zastanówmy się, dlaczego nie zostały one zbuforowane. Chodzi o to, że Docker sprawdza warstwę po warstwie, czy polecenia i związane z nimi pliki się nie zmieniły. Na czwartym kroku kopiujemy wszystkie pliki naszego projektu, a wśród nich, oczywiście, są zmiany, dlatego Docker nie tylko nie korzysta z pamięci podręcznej dla tej warstwy, ale także dla wszystkich kolejnych! Wprowadźmy niewielkie zmiany w Dockerfile.

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Najpierw kopiowane są package.json i package-lock.json, następnie instalowane są zależności, a dopiero potem kopiowany jest cały projekt. W rezultacie:

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/12 : FROM node:12.16.2-alpine3.11 as builder
Step 2/12 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Using cache
Step 3/12 : WORKDIR /app
 ---> Using cache
Step 4/12 : COPY package*.json ./
 ---> Using cache
Step 5/12 : RUN npm ci
 ---> Using cache
Step 6/12 : COPY . .
Step 7/12 : RUN npm run build --prod
Date: 2020-04-16T21:29:44.770Z - Hash: fffa0fddaa3425c55dd3 - Time: 38287ms
 ---> 1b9448c73558
Step 8/12 : FROM nginx:stable-alpine
Step 9/12 : WORKDIR /app
 ---> Using cache
Step 10/12 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Using cache
Step 11/12 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Using cache
Step 12/12 : COPY --from=builder /app/dist/app .
Successfully built a44dd7c217c3
Successfully tagged app:latest

real 0m46.497s
user 0m0.000s
sys 0m0.000s

46 sekund zamiast 3 minut — znacznie lepiej! Ważna jest właściwa kolejność warstw: najpierw kopiujemy to, co się nie zmienia, następnie to, co rzadko się zmienia, a na końcu — to, co zmienia się często.

Dalej kilka słów o budowie obrazów w systemach CI/CD.

Wykorzystanie poprzednich obrazów jako pamięci podręcznej

Jeśli używamy do budowy jakiegoś rozwiązania SaaS, lokalna pamięć podręczna Dockera może być czysta i świeża. Aby Docker miał skąd wziąć upieczone warstwy, przekaż mu poprzedni zbudowany obraz.

Rozważmy na przykład budowę naszego aplikacji w GitHub Actions. Użyjemy takiej konfiguracji

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Build
      run: |
        docker build 
          -t $IMAGE_NAME:$IMAGE_TAG 
          -t $IMAGE_NAME:latest 
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

Obraz jest budowany i wysyłany do GitHub Packages w ciągu dwóch minut i 20 sekund:

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Teraz zmienimy budowę, aby korzystać z pamięci podręcznej opartej na wcześniejszych zbudowanych obrazach:

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Pull latest images
      run: |
        docker pull $IMAGE_NAME:latest || true
        docker pull $IMAGE_NAME-builder-stage:latest || true

    - name: Images list
      run: |
        docker images

    - name: Build
      run: |
        docker build 
          --target builder 
          --cache-from $IMAGE_NAME-builder-stage:latest 
          -t $IMAGE_NAME-builder-stage 
          .
        docker build 
          --cache-from $IMAGE_NAME-builder-stage:latest 
          --cache-from $IMAGE_NAME:latest 
          -t $IMAGE_NAME:$IMAGE_TAG 
          -t $IMAGE_NAME:latest 
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME-builder-stage:latest
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

Na początek musimy wyjaśnić, dlaczego uruchamiane są dwie komendy build. Chodzi o to, że w budowie wielostanowej rezultatem będą warstwy z ostatniego stanu. W tym przypadku warstwy z poprzednich stanów nie trafią do obrazu. Dlatego korzystając z finalnego obrazu z poprzedniej budowy, Docker nie będzie mógł znaleźć gotowych warstw do zbudowania obrazu z nodejs (stan builder). Aby rozwiązać ten problem, tworzony jest obraz pośredni $IMAGE_NAME-builder-stage i jest wysyłany do GitHub Packages, aby można go było wykorzystać w kolejnej budowie jako źródło pamięci podręcznej.

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Całkowity czas budowy skrócił się do półtorej minuty. Pół minuty marnowane jest na pobranie wcześniejszych obrazów.

Wstępne tworzenie obrazów

Inny sposób na rozwiązanie problemu czystej pamięci podręcznej Dockera to przeniesienie części warstw do innego pliku Dockerfile, zbudowanie go oddzielnie, wypchnięcie do Container Registry i użycie jako nadrzędnego.

Tworzymy nasz obraz nodejs do budowy aplikacji Angular. Tworzymy w projekcie Dockerfile.node

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++

Budujemy i wypychamy publiczny obraz do Docker Hub:

docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest

Teraz w naszym głównym Dockerfile używamy gotowego obrazu:

FROM exsmund/node-for-angular:latest as builder
...

W naszym przykładzie czas budowy się nie zmniejszył, ale wstępnie utworzone obrazy mogą być przydatne, jeśli masz wiele projektów i w każdym z nich musisz zainstalować te same zależności.

Kilka wskazówek, jak przyspieszyć budowanie obrazów Dockera. Na przykład, do 30 sekund

Rozpatrzyliśmy kilka metod przyspieszania budowy obrazów dockera. Jeśli chcesz, aby wdrożenie było szybkie, spróbuj zastosować w swoim projekcie:

  • zmniejszenie kontekstu;
  • używanie małych obrazów nadrzędnych;
  • budowę wieloetapową;
  • zmianę kolejności instrukcji w Dockerfile, aby efektywnie wykorzystać pamięć podręczną;
  • konfigurację pamięci podręcznej w systemach CI/CD;
  • wstępne tworzenie obrazów.

Mam nadzieję, że na tym przykładzie stanie się jasne, jak działa Docker, i będziesz mógł optymalnie skonfigurować swoje wdrożenie. Aby móc bawić się przykładami z artykułu, stworzono repozytorium https://github.com/devopsprodigy/test-docker-build.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster