Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Avant qu'une fonctionnalité n'atteigne la production, dans notre époque d'orchestrateurs complexes et de CI/CD, elle doit passer par un long chemin depuis le commit jusqu'aux tests et à la livraison. Auparavant, il était possible de transférer de nouveaux fichiers par FTP (ce qui n'est plus la norme, n'est-ce pas ?), et le processus de "déploiement" ne prenait que quelques secondes. Maintenant, il faut créer une demande de fusion et attendre un certain temps avant que la fonctionnalité ne parvienne aux utilisateurs.

Une partie de ce parcours consiste à construire une image Docker. Parfois, la construction dure des minutes, parfois des dizaines de minutes, ce qui est difficilement acceptable. Dans cet article, nous prendrons une application simple que nous emballerons dans une image, appliquerons plusieurs méthodes pour accélérer la construction et examinerons les nuances de ces méthodes.

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Nous avons une bonne expĂ©rience de crĂ©ation et de maintenance de sites d'information : TASS, The Bell, "Novaya Gazeta", Republic
 RĂ©cemment, nous avons complĂ©tĂ© notre portefeuille en lançant en production le site Reminder. Et pendant que nous peaufinions rapidement de nouvelles fonctionnalitĂ©s et corrigions d'anciens bugs, le dĂ©ploiement lent est devenu un gros problĂšme.

Nous effectuons le dĂ©ploiement sur GitLab. Nous construisons des images, les poussons dans le GitLab Registry et les dĂ©ployons en production. Dans cette liste, le plus long est la construction des images. À titre d'exemple : sans optimisation, chaque construction du backend prenait 14 minutes.

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Finalement, il est devenu clair qu'il était impossible de continuer ainsi, nous avons donc décidé d'explorer pourquoi les images prenaient autant de temps à se construire. Au final, nous avons réussi à réduire le temps de construction à 30 secondes !

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Pour cet article, afin de ne pas ĂȘtre liĂ© Ă  l'environnement de Reminder, nous examinerons un exemple de construction d'une application vide sur Angular. Donc, crĂ©ons notre application :

ng n app

Ajoutons-lui PWA (nous sommes progressistes) :

ng add @angular/pwa --project app

Pendant que des millions de paquets npm sont tĂ©lĂ©chargĂ©s, examinons comment fonctionne une image docker. Docker permet d'emballer des applications et de les exĂ©cuter dans un environnement isolĂ©, appelĂ© conteneur. GrĂące Ă  l'isolation, il est possible d'exĂ©cuter plusieurs conteneurs en mĂȘme temps sur un mĂȘme serveur. Les conteneurs sont beaucoup plus lĂ©gers que les machines virtuelles, car ils s'exĂ©cutent directement sur le noyau du systĂšme. Pour exĂ©cuter un conteneur avec notre application, nous devons d'abord crĂ©er une image dans laquelle nous emballons tout ce qui est nĂ©cessaire au fonctionnement de notre application. En gros, une image est une copie de la structure du systĂšme de fichiers. Par exemple, prenons un Dockerfile :

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

Un Dockerfile est un ensemble d'instructions ; en exécutant chacune d'elles, Docker sauvegardera les modifications dans le systÚme de fichiers et les appliquera aux précédentes. Chaque commande crée sa propre couche. L'image finale est la combinaison de toutes ces couches.

Ce qu'il est important de savoir : chaque couche peut ĂȘtre mise en cache par Docker. Si rien n'a changĂ© depuis la derniĂšre construction, Docker utilisera la couche dĂ©jĂ  prĂ©parĂ©e au lieu d'exĂ©cuter la commande. Comme le principal gain de vitesse de construction proviendra de l'utilisation du cache, nous nous concentrerons sur la construction de l'image avec le cache dĂ©jĂ  prĂȘt. Ainsi, de maniĂšre chronologique :

  1. Nous supprimons les images localement pour que les exécutions précédentes n'affectent pas le test.
    docker rmi $(docker images -q)
  2. Nous lançons la construction pour la premiÚre fois.
    time docker build -t app .
  3. Nous modifions le fichier src/index.html — nous simulons le travail d'un programmeur.
  4. Nous lançons la construction une deuxiÚme fois.
    time docker build -t app .

Si l'environnement de construction des images est correctement configurĂ© (nous en parlerons un peu plus bas), Docker aura dĂ©jĂ  un tas de caches disponibles lors du lancement de la construction. Notre objectif est d'apprendre Ă  utiliser le cache afin que la construction se fasse le plus rapidement possible. Comme nous supposons que le lancement de la construction sans cache n'a lieu qu'une seule fois — la toute premiĂšre — nous pouvons ignorer la lenteur de ce premier lancement. Dans nos tests, ce qui compte, c'est la seconde exĂ©cution de la construction, lorsque les caches sont dĂ©jĂ  radiĂ©s et que nous sommes prĂȘts Ă  cuire notre gĂąteau. Cependant, quelques conseils auront Ă©galement un impact sur la premiĂšre construction.

Mettons le Dockerfile décrit ci-dessus dans le dossier du projet et lançons la construction. Tous les listings fournis ont été abrégés pour faciliter la lecture.

$ time docker build -t app .
Envoi du contexte de construction au démon Docker 409 Mo
Étape 1/5 : FROM node:12.16.2
Statut : Image plus récente téléchargée pour node:12.16.2
Étape 2/5 : WORKDIR /app
Étape 3/5 : COPY . .
Étape 4/5 : RUN npm ci
1357 paquets ajoutés en 22,47 s
Étape 5/5 : RUN npm run build --prod
Date : 2020-04-16T19:20:09.664Z - Hash : fffa0fddaa3425c55dd3 - Temps : 37581ms
Construit avec succĂšs c8c279335f46
ÉtiquetĂ© avec succĂšs app:latest

réel 5m4.541s
utilisateur 0m0.000s
systĂšme 0m0.000s

Nous modifions le contenu de src/index.html et lançons une deuxiÚme fois.

$ time docker build -t app .
Envoi du contexte de construction au démon Docker 409 Mo
Étape 1/5 : FROM node:12.16.2
Étape 2/5 : WORKDIR /app
 ---> Utilisation du cache
Étape 3/5 : COPY . .
Étape 4/5 : RUN npm ci
1357 paquets ajoutés en 22,47 s
Étape 5/5 : RUN npm run build --prod
Date : 2020-04-16T19:26:26.587Z - Hash : fffa0fddaa3425c55dd3 - Temps : 37902ms
Construit avec succĂšs 79f335df92d3
ÉtiquetĂ© avec succĂšs app:latest

réel 3m33.262s
utilisateur 0m0.000s
systĂšme 0m0.000s

Pour vérifier si notre image a été créée avec succÚs, exécutons la commande docker images:

RÉPERTOIRE   TAG      ID DE L'IMAGE       CRÉÉ              TAILLE
app          latest   79f335df92d3   Il y a environ une minute   1.74 Go

Avant de construire, Docker prend tous les fichiers dans le contexte actuel et les envoie Ă  son dĂ©mon. Envoi du contexte de construction au dĂ©mon Docker 409 Mo. Le contexte de construction est spĂ©cifiĂ© comme le dernier argument de la commande build. Dans notre cas, il s'agit du rĂ©pertoire courant — «. » — et Docker tire tout ce que nous avons dans ce dossier. 409 Mo, c'est beaucoup : rĂ©flĂ©chissons Ă  comment optimiser cela.

Réduisons le contexte

Pour rĂ©duire le contexte, il y a deux options. Soit nous plaçons tous les fichiers nĂ©cessaires Ă  la construction dans un dossier sĂ©parĂ© et nous indiquons le contexte de Docker sur ce dossier. Cela peut ne pas toujours ĂȘtre pratique, donc il est possible de spĂ©cifier des exceptions : ce qui ne doit pas ĂȘtre inclus dans le contexte. Pour cela, nous allons crĂ©er un fichier .dockerignore et indiquer ce qui n'est pas nĂ©cessaire pour la construction :

.git
/node_modules

et nous allons relancer la construction :

$ time docker build -t app .
Envoi du contexte de construction au démon Docker 607.2 Ko
Étape 1/5 : FROM node:12.16.2
Étape 2/5 : WORKDIR /app
 ---> Utilisation du cache
Étape 3/5 : COPY . .
Étape 4/5 : RUN npm ci
Ajout de 1357 paquets en 22.47s
Étape 5/5 : RUN npm run build --prod
Date : 2020-04-16T19:33:54.338Z - Hash : fffa0fddaa3425c55dd3 - Temps : 37313ms
Construit avec succĂšs 4942f010792a
Tagué avec succÚs app:latest

réel 1m47.763s
utilisateur 0m0.000s
systĂšme 0m0.000s

607.2 Ko — bien mieux que 409 Mo. De plus, nous avons rĂ©duit la taille de l'image de 1.74 Ă  1.38 Go :

DÉPÔT   TAG      ID D'IMAGE       CRÉÉ         TAILLE
app          latest   4942f010792a   il y a 3 minutes   1.38GB

Essayons encore de réduire la taille de l'image.

Utilisons Alpine

Une autre façon d'économiser sur la taille de l'image est d'utiliser une petite image parent. L'image parent est l'image sur laquelle notre image est construite. Le niveau inférieur est spécifié par la commande DE dans le Dockerfile. Dans notre cas, nous utilisons une image basée sur Ubuntu, qui a déjà Node.js installé. Et elle pÚse ...

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

... presque un gigaoctet. On peut réduire considérablement l'espace en utilisant une image basée sur Alpine Linux. Alpine est un Linux trÚs léger. L'image Docker pour Node.js basée sur Alpine ne pÚse que 88.5 Mo. Alors, remplaçons notre lourde image par :

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

Nous avons dû installer certaines choses nécessaires à la construction de l'application. Oui, Angular ne se construit pas sans Python ¯(°_o)\/¯

Mais au moins, la taille de l'image a diminué de 150 Mo :

DÉPÔT   TAG      ID D'IMAGE       CRÉÉ          TAILLE
app          latest   aa031edc315a   il y a 22 minutes   761MB

Allons plus loin.

Construction multi-étapes

Tout ce qui se trouve dans l'image n'est pas nécessaire en production.

$ 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

Avec docker run app ls -lah Nous avons lancé un conteneur basé sur notre image app et avons exécuté la commande ls -lah, aprÚs quoi le conteneur a terminé son travail.

En production, nous avons seulement besoin du dossier dist. Cependant, les fichiers doivent ĂȘtre accessibles Ă  l'extĂ©rieur. Nous pouvons dĂ©marrer un serveur HTTP sur Node.js. Mais nous allons simplifier les choses. Devinez le mot russe qui contient quatre lettres «ы». Correct ! Đ«ĐœĐ¶Ń‹ĐœŃ‹Đșсы. Prenons une image avec Nginx, mettons-y le dossier dist et une petite configuration :

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

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

Tout cela sera facilité par un multi-stage build. Modifions notre 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 .

Maintenant, nous avons deux instructions DE dans le Dockerfile, chacune d'elles lance sa propre étape de construction. La premiÚre s'appelle builder, et à partir du dernier FROM, notre image finale sera préparée. En derniÚre étape, nous copions l'artéfact de notre construction de l'étape précédente dans l'image finale avec Nginx. La taille de l'image a été considérablement réduite :

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

Lançons un conteneur avec notre image et vérifions que tout fonctionne :

docker run -p8080:80 app

Avec l'option -p8080:80, nous avons redirigĂ© le port 8080 de notre machine hĂŽte vers le port 80 Ă  l'intĂ©rieur du conteneur, oĂč Nginx fonctionne. Ouvrons dans le navigateur http://localhost:8080/ et voyons notre application. Tout fonctionne !

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Réduire la taille de l'image de 1,74 Go à 36 Mo diminue considérablement le temps de livraison de votre application en production. Mais revenons au temps de construction.

$ 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

Changeons l'ordre des couches

Les trois premiĂšres Ă©tapes ont Ă©tĂ© mises en cache (indice Using cache). À la quatriĂšme Ă©tape, tous les fichiers du projet sont copiĂ©s et Ă  la cinquiĂšme, les dĂ©pendances sont installĂ©es RUN npm ci — un total de 47,338 s. Pourquoi installer les dĂ©pendances Ă  chaque fois alors qu'elles changent trĂšs rarement ? Voyons pourquoi elles n'ont pas Ă©tĂ© mises en cache. En fait, Docker vĂ©rifie couche par couche si la commande et les fichiers associĂ©s ont changĂ©. À la quatriĂšme Ă©tape, nous copions tous les fichiers de notre projet et, bien sĂ»r, il y a des modifications, donc Docker ne prend pas seulement pas cette couche dans le cache, mais toutes les suivantes ! Faisons quelques petites modifications dans le 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 .

D'abord, package.json et package-lock.json sont copiés, puis les dépendances sont installées, et seulement aprÚs cela, l'ensemble du projet est copié. En conséquence :

$ 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 secondes au lieu de 3 minutes — c'est bien mieux ! L'ordre des couches est crucial : nous copions d'abord ce qui ne change pas, puis ce qui change rarement, et enfin ce qui change souvent.

Ensuite, quelques mots sur la construction des images dans les systĂšmes CI/CD.

Utilisation des images précédentes pour le cache

Si nous utilisons une solution SaaS pour la construction, le cache Docker local peut ĂȘtre propre et frais. Pour que Docker puisse rĂ©cupĂ©rer des couches dĂ©jĂ  cuites, donnez-lui l'image prĂ©cĂ©dente construite.

Prenons l'exemple de la construction de notre application dans GitHub Actions. Utilisons cette configuration.

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

L'image se construit et se pousse dans GitHub Packages en deux minutes et 20 secondes :

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Nous allons maintenant modifier la construction pour utiliser le cache basé sur les images précédemment construites :

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

Pour commencer, il faut expliquer pourquoi deux commandes sont lancĂ©es. buildEn effet, dans une construction multi-Ă©tapes, le rĂ©sultat sera un ensemble de couches du dernier stade. Les couches des stades prĂ©cĂ©dents ne seront pas incluses dans l'image. Ainsi, lorsque l'image finale de la construction prĂ©cĂ©dente est utilisĂ©e, Docker ne peut pas trouver les couches prĂȘtes pour construire l'image avec nodejs (stade builder). Pour rĂ©soudre ce problĂšme, une image intermĂ©diaire est créée. $IMAGE_NAME-builder-stage et est envoyĂ©e dans GitHub Packages afin de pouvoir ĂȘtre utilisĂ©e dans la construction suivante comme source de cache.

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Le temps total de construction a été réduit à une minute et demie. Une demi-minute est consacrée à récupérer les images précédentes.

Création d'images préliminaires

Une autre façon de résoudre le problÚme de cache Docker propre est de déplacer une partie des couches dans un autre Dockerfile, de le construire séparément, de le pousser dans le Container Registry et de l'utiliser comme parent.

Créons notre propre image nodejs pour construire une application Angular. Créez un Dockerfile.node dans le projet.

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

Construisons et poussons l'image publique sur Docker Hub :

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

Maintenant, dans notre Dockerfile principal, utilisons l'image prĂȘte Ă  l'emploi :

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

Dans notre exemple, le temps de construction n'a pas diminuĂ©, mais les images prĂ©-créées peuvent ĂȘtre utiles si vous avez de nombreux projets et que chacun d'eux nĂ©cessite les mĂȘmes dĂ©pendances.

Quelques conseils pour accélérer la construction des images Docker. Par exemple, jusqu'à 30 secondes

Nous avons examiné plusieurs méthodes pour accélérer la construction des images Docker. Si vous souhaitez que le déploiement se fasse rapidement, essayez d'appliquer dans votre projet :

  • la rĂ©duction du contexte ;
  • l'utilisation de petites images parentales ;
  • la construction multi-Ă©tapes ;
  • le changement de l'ordre des instructions dans le Dockerfile pour utiliser efficacement le cache ;
  • la configuration du cache dans les systĂšmes CI/CD ;
  • la crĂ©ation prĂ©liminaire d'images.

J'espÚre que cet exemple clarifie le fonctionnement de Docker et que vous pourrez configurer votre déploiement de maniÚre optimale. Pour expérimenter avec les exemples de cet article, un dépÎt a été créé. https://github.com/devopsprodigy/test-docker-build.

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster