Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Sebelum sesuatu ciri mula diterbitkan, pada zaman pengaturcara yang kompleks dan CI/CD ini, terdapat banyak jalan untuk pergi daripada komit kepada ujian dan penghantaran. Sebelum ini, anda boleh memuat naik fail baharu melalui FTP (tiada siapa yang melakukannya lagi, bukan?), dan proses "pengerahan" mengambil masa beberapa saat. Kini anda perlu membuat permintaan gabungan dan menunggu lama untuk ciri itu sampai kepada pengguna.

Sebahagian daripada laluan ini sedang membina imej Docker. Kadang-kadang perhimpunan berlangsung beberapa minit, kadang-kadang berpuluh-puluh minit, yang hampir tidak boleh dipanggil biasa. Dalam artikel ini, kami akan mengambil aplikasi mudah yang akan kami bungkus ke dalam imej, menggunakan beberapa kaedah untuk mempercepatkan binaan dan melihat nuansa cara kaedah ini berfungsi.

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Kami mempunyai pengalaman yang baik dalam mencipta dan menyokong laman web media: TASS, Bell, "Akhbar Baharu", Republik… Tidak lama dahulu kami mengembangkan portfolio kami dengan mengeluarkan tapak web produk Peringatan. Dan sementara ciri baharu ditambah dengan cepat dan pepijat lama telah diperbaiki, penggunaan yang perlahan menjadi masalah besar.

Kami menggunakan GitLab. Kami mengumpul imej, menolaknya ke GitLab Registry dan melancarkannya ke pengeluaran. Perkara terpanjang dalam senarai ini ialah memasang imej. Contohnya: tanpa pengoptimuman, setiap binaan bahagian belakang mengambil masa 14 minit.

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Pada akhirnya, menjadi jelas bahawa kami tidak lagi boleh hidup seperti ini, dan kami duduk untuk memikirkan mengapa imej mengambil masa yang lama untuk dikumpulkan. Hasilnya, kami berjaya mengurangkan masa perhimpunan kepada 30 saat!

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Untuk artikel ini, agar tidak terikat dengan persekitaran Peringatan, mari lihat contoh memasang aplikasi Sudut kosong. Jadi, mari buat aplikasi kami:

ng n app

Tambahkan PWA padanya (kami progresif):

ng add @angular/pwa --project app

Semasa sejuta pakej npm sedang dimuat turun, mari kita fikirkan cara imej docker berfungsi. Docker menyediakan keupayaan untuk membungkus aplikasi dan menjalankannya dalam persekitaran terpencil yang dipanggil bekas. Terima kasih kepada pengasingan, anda boleh menjalankan banyak bekas secara serentak pada satu pelayan. Bekas jauh lebih ringan daripada mesin maya kerana ia berjalan terus pada kernel sistem. Untuk menjalankan bekas dengan aplikasi kami, pertama sekali kami perlu mencipta imej di mana kami akan membungkus semua yang diperlukan untuk aplikasi kami berjalan. Pada asasnya, imej adalah salinan sistem fail. Sebagai contoh, ambil Dockerfile:

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

Fail Docker ialah satu set arahan; Dengan melakukan setiap satu daripadanya, Docker akan menyimpan perubahan pada sistem fail dan menindihnya pada yang sebelumnya. Setiap pasukan mencipta lapisan sendiri. Dan imej siap adalah lapisan digabungkan bersama.

Apa yang penting untuk diketahui: setiap lapisan Docker boleh cache. Jika tiada apa-apa yang berubah sejak binaan terakhir, maka bukannya melaksanakan arahan, docker akan mengambil lapisan siap sedia. Memandangkan peningkatan utama dalam kelajuan binaan akan disebabkan oleh penggunaan cache, apabila mengukur kelajuan binaan kita akan memberi perhatian khusus untuk membina imej dengan cache siap sedia. Jadi, langkah demi langkah:

  1. Kami memadamkan imej secara setempat supaya larian sebelumnya tidak menjejaskan ujian.
    docker rmi $(docker images -q)
  2. Kami melancarkan binaan buat kali pertama.
    time docker build -t app .
  3. Kami menukar fail src/index.html - kami meniru kerja seorang pengaturcara.
  4. Kami menjalankan binaan untuk kali kedua.
    time docker build -t app .

Jika persekitaran untuk membina imej dikonfigurasikan dengan betul (lebih lanjut mengenai perkara di bawah), maka apabila binaan bermula, Docker akan mempunyai banyak cache pada papan. Tugas kami adalah untuk mempelajari cara menggunakan cache supaya binaan berjalan secepat mungkin. Memandangkan kita mengandaikan bahawa menjalankan binaan tanpa cache hanya berlaku sekaliβ€”kali pertamaβ€”maka kita boleh mengabaikan betapa lambatnya kali pertama itu. Dalam ujian, larian kedua binaan adalah penting kepada kami, apabila cache sudah dipanaskan dan kami bersedia untuk membakar kek kami. Walau bagaimanapun, beberapa petua juga akan mempengaruhi binaan pertama.

Mari letakkan Dockerfile yang diterangkan di atas dalam folder projek dan mulakan binaan. Semua penyenaraian telah dipadatkan untuk memudahkan pembacaan.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Status: Downloaded newer image for node:12.16.2
Step 2/5 : WORKDIR /app
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Time: 37581ms
Successfully built c8c279335f46
Successfully tagged app:latest

real 5m4.541s
user 0m0.000s
sys 0m0.000s

Kami menukar kandungan src/index.html dan menjalankannya buat kali kedua.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Time: 37902ms
Successfully built 79f335df92d3
Successfully tagged app:latest

real 3m33.262s
user 0m0.000s
sys 0m0.000s

Untuk melihat sama ada kita mempunyai imej, jalankan arahan docker images:

REPOSITORY   TAG      IMAGE ID       CREATED              SIZE
app          latest   79f335df92d3   About a minute ago   1.74GB

Sebelum membina, docker mengambil semua fail dalam konteks semasa dan menghantarnya ke daemonnya Sending build context to Docker daemon 409MB. Konteks binaan ditentukan sebagai hujah terakhir kepada arahan bina. Dalam kes kami, ini ialah direktori semasa - ".", - dan Docker menyeret semua yang kami ada dalam folder ini. 409 MB adalah banyak: mari kita fikirkan cara untuk membetulkannya.

Mengurangkan konteks

Untuk mengurangkan konteks, terdapat dua pilihan. Atau letakkan semua fail yang diperlukan untuk pemasangan dalam folder berasingan dan arahkan konteks docker ke folder ini. Ini mungkin tidak selalunya mudah, jadi adalah mungkin untuk menentukan pengecualian: perkara yang tidak boleh diseret ke dalam konteks. Untuk melakukan ini, letakkan fail .dockerignore dalam projek dan nyatakan perkara yang tidak diperlukan untuk binaan:

.git
/node_modules

dan jalankan binaan semula:

$ time docker build -t app .
Sending build context to Docker daemon 607.2kB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Time: 37313ms
Successfully built 4942f010792a
Successfully tagged app:latest

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

607.2 KB jauh lebih baik daripada 409 MB. Kami juga mengurangkan saiz imej daripada 1.74 kepada 1.38 GB:

REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app          latest   4942f010792a   3 minutes ago   1.38GB

Cuba kita kecilkan lagi saiz gambar.

Kami menggunakan Alpine

Satu lagi cara untuk menjimatkan saiz imej ialah menggunakan imej induk kecil. Imej ibu bapa ialah imej berdasarkan imej kami disediakan. Lapisan bawah ditentukan oleh arahan FROM dalam Dockerfile. Dalam kes kami, kami menggunakan imej berasaskan Ubuntu yang telah memasang nodej. Dan beratnya...

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB

... hampir gigabait. Anda boleh mengurangkan kelantangan dengan ketara dengan menggunakan imej berdasarkan Alpine Linux. Alpine ialah Linux yang sangat kecil. Imej docker untuk nodej berdasarkan alpine beratnya hanya 88.5 MB. Oleh itu, mari kita gantikan imej meriah kita di rumah:

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

Kami terpaksa memasang beberapa perkara yang perlu untuk membina aplikasi. Ya, Angular tidak membina tanpa Python Β―(Β°_o)/Β―

Tetapi saiz imej menurun kepada 150 MB:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   aa031edc315a   22 minutes ago   761MB

Mari pergi lebih jauh.

Perhimpunan pelbagai peringkat

Tidak semua yang ada dalam imej adalah apa yang kita perlukan dalam pengeluaran.

$ 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

Dengan docker run app ls -lah kami melancarkan bekas berdasarkan imej kami app dan melaksanakan perintah di dalamnya ls -lah, selepas itu bekas itu menyelesaikan kerjanya.

Dalam pengeluaran kita hanya memerlukan folder dist. Dalam kes ini, fail entah bagaimana perlu diberikan di luar. Anda boleh menjalankan beberapa pelayan HTTP pada nodejs. Tetapi kami akan memudahkannya. Teka perkataan Rusia yang mempunyai empat huruf "y". Betul! Ynzhynyksy. Mari ambil imej dengan nginx, letakkan folder di dalamnya dist dan konfigurasi kecil:

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

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

Binaan berbilang peringkat akan membantu kami melakukan semua ini. Mari tukar fail Docker kami:

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 .

Sekarang kita mempunyai dua arahan FROM dalam Dockerfile, setiap daripada mereka menjalankan langkah binaan yang berbeza. Kami memanggil yang pertama builder, tetapi bermula dari FROM terakhir, imej akhir kami akan disediakan. Langkah terakhir ialah menyalin artifak pemasangan kami dalam langkah sebelumnya ke dalam imej akhir dengan nginx. Saiz imej telah berkurangan dengan ketara:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   2c6c5da07802   29 minutes ago   36MB

Mari jalankan bekas dengan imej kami dan pastikan semuanya berfungsi:

docker run -p8080:80 app

Menggunakan pilihan -p8080:80, kami memajukan port 8080 pada mesin hos kami ke port 80 di dalam bekas tempat nginx dijalankan. Buka dalam penyemak imbas http://localhost:8080/ dan kami melihat permohonan kami. Semuanya berfungsi!

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Mengurangkan saiz imej daripada 1.74 GB kepada 36 MB dengan ketara mengurangkan masa yang diperlukan untuk menghantar aplikasi anda ke pengeluaran. Tetapi mari kita kembali ke masa perhimpunan.

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

real 2m17.700s
user 0m0.000s
sys 0m0.000s

Menukar susunan lapisan

Tiga langkah pertama kami telah di-cache (petunjuk Using cache). Pada langkah keempat, semua fail projek disalin dan pada kebergantungan langkah kelima dipasang RUN npm ci - sebanyak 47.338s. Mengapa memasang semula kebergantungan setiap kali jika ia berubah sangat jarang? Mari kita ketahui mengapa mereka tidak dicache. Intinya ialah Docker akan menyemak lapisan demi lapisan untuk melihat sama ada arahan dan fail yang berkaitan dengannya telah berubah. Pada langkah keempat, kami menyalin semua fail projek kami, dan di antara mereka, sudah tentu, terdapat perubahan, jadi Docker bukan sahaja tidak mengambil lapisan ini dari cache, tetapi juga semua yang berikutnya! Mari buat beberapa perubahan kecil pada 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 .

Mula-mula, package.json dan package-lock.json disalin, kemudian kebergantungan dipasang, dan hanya selepas itu keseluruhan projek disalin. Akibatnya:

$ 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 saat dan bukannya 3 minit - jauh lebih baik! Susunan lapisan yang betul adalah penting: mula-mula kita menyalin apa yang tidak berubah, kemudian apa yang jarang berubah, dan akhirnya apa yang sering berubah.

Seterusnya, beberapa perkataan tentang memasang imej dalam sistem CI/CD.

Menggunakan imej sebelumnya untuk cache

Jika kami menggunakan beberapa jenis penyelesaian SaaS untuk binaan, maka cache Docker tempatan mungkin bersih dan segar. Untuk memberi tempat berlabuh untuk mendapatkan lapisan yang dibakar, berikan dia imej yang dibina sebelumnya.

Mari kita ambil contoh membina aplikasi kita dalam Tindakan GitHub. Kami menggunakan konfigurasi ini

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

Imej dipasang dan ditolak ke Pakej GitHub dalam masa dua minit dan 20 saat:

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Sekarang mari kita ubah binaan supaya cache digunakan berdasarkan imej terbina sebelumnya:

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

Mula-mula kami perlu memberitahu anda mengapa dua arahan dilancarkan build. Hakikatnya ialah dalam perhimpunan berbilang peringkat imej yang terhasil akan menjadi satu set lapisan dari peringkat terakhir. Dalam kes ini, lapisan daripada lapisan sebelumnya tidak akan disertakan dalam imej. Oleh itu, apabila menggunakan imej akhir daripada binaan sebelumnya, Docker tidak akan dapat mencari lapisan sedia untuk membina imej dengan nodejs (peringkat pembina). Untuk menyelesaikan masalah ini, imej perantaraan dicipta $IMAGE_NAME-builder-stage dan ditolak ke Pakej GitHub supaya ia boleh digunakan dalam binaan seterusnya sebagai sumber cache.

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Jumlah masa perhimpunan dikurangkan kepada satu setengah minit. Setengah minit dihabiskan untuk menarik imej sebelumnya.

praimej

Satu lagi cara untuk menyelesaikan masalah cache Docker yang bersih adalah dengan mengalihkan beberapa lapisan ke dalam Dockerfile yang lain, membinanya secara berasingan, menolaknya ke dalam Container Registry dan menggunakannya sebagai induk.

Kami mencipta imej nodejs kami sendiri untuk membina aplikasi Angular. Cipta Dockerfile.node dalam projek

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

Kami mengumpul dan menolak imej awam dalam Docker Hub:

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

Sekarang dalam Dockerfile utama kami, kami menggunakan imej siap:

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

Dalam contoh kami, masa binaan tidak berkurangan, tetapi imej pra-bina boleh berguna jika anda mempunyai banyak projek dan perlu memasang kebergantungan yang sama dalam setiap satu daripadanya.

Beberapa petua tentang cara mempercepatkan membina imej Docker. Contohnya, sehingga 30 saat

Kami melihat beberapa kaedah untuk mempercepatkan pembinaan imej docker. Jika anda mahu penyebaran berjalan dengan cepat, cuba gunakan ini dalam projek anda:

  • mengurangkan konteks;
  • penggunaan imej induk kecil;
  • perhimpunan berbilang peringkat;
  • menukar susunan arahan dalam Dockerfile untuk menggunakan cache dengan cekap;
  • menyediakan cache dalam sistem CI/CD;
  • penciptaan awal imej.

Saya harap contoh itu akan menjelaskan cara Docker berfungsi, dan anda akan dapat mengkonfigurasi penggunaan anda secara optimum. Untuk bermain dengan contoh dari artikel, repositori telah dibuat https://github.com/devopsprodigy/test-docker-build.

Sumber: www.habr.com

Tambah komen