ng'stuff

🔭 🎶 🕺

Pourquoi Docker ?

Pour travailler ensemble

Tout le monde développe sur différentes machines. Il n’est pas rare que plusieurs collègues aient installé différentes version d’une même library, d’un package manager, du runtime de leur application…​ (cc node/npm 👋)

Lorsque l’application de développement est dockerisée, tous les devs (et la prod !) sont soumis aux mêmes règles, ce qui évite le syndrôme de "mais ça marche sur mon PC" tout en réduisant la configuration nécessaire à installer l’environnement de dev. Voir juste après !

Pour moins perdre de temps à configurer

La configuration, c’est du temps passé à ne pas être productif. Plus il y a d’environnements dans lesquels une application doit tourner, plus il y a de configuration à faire. Docker permet d’abstraire l’environnement dans lequel tourne l’application et donc de ne faire que le minimum nécessaire.

Une conséquence notable : pour ajouter un service à la stack, plus forcément besoin d’apprendre en détails une technologie. Il suffit d’ajouter une image existante de celle-ci, et de simplement spécifier les variables d’environnement nécessaires. Par exemple, pour ajouter une database PostgreSQL, il suffit au minimum de renseigner un root password et de monter un volume pour persister les données.

Pour séparer les problèmes

Comme chaque service est aussi simple que possible, chacun n’a que très peu de dépendances avec le reste du monde. On voit que, par nature, les conteneurs nous poussent à n’avoir que le minimum vital de dépendances entre services.

Par dépendances, je pense en particulier à :

  • Système, programmes, libraries installées
  • Configurations associées à ce setup
  • Volumes (fichiers)
  • Accès réseau

Pour gérer et sécuriser les connexions réseau

Car oui : c’est vite fait de faire fuiter le port d’une database, et quand elle n’est pas sécurisée c’est gênant 😬

L’idée est de penser un service comme une boîte noire qui expose un ou plusieurs ports. Les services communiquent entre eux grâce aux volumes montés en commun ou à travers des ports internes. En effet, les services appartenant à un même réseau peuvent par défaut s’accéder entre eux.

Pièges et trucs à savoir

Sous Windows et Mac, les conteneurs tournent sur une VM. Du coup c’est lent, pas natif et les lectures/écritures disque peuvent ralentir l’édition de fichiers. La solution est simple : utilise Linux 😏 (ou un Windows/Mac puissant)

Lorsqu’un même volume (dossier) est monté en écriture dans deux conteneurs à la fois, on peut se retrouver avec des comportements inattendus : chez moi, l’intégralité du dossier généré par un run de build disparaissait en même temps que le conteneur à la fin du run. Je recommande donc vivement de ne monter en écriture qu’un seul fichier ou dossier à la fois.

C’est possible de ne partager en écriture qu’une partie d’une même arborescence commune. Par exemple, si backend et frontend partagent code en lecture mais que backend doit écrire dans code/target et frontend doit écrire dans code/dist, alors les volumes peuvent être surchargés comme ceci :

yml
backend:
  volumes:
    - ./code:/code:ro
    - ./code/target:/code/target

webapp:
  volumes:
    - ./code:/code:ro
    - ./code/dist:/code/dist

Les images Docker peuvent vite prendre de l’espace disque. Mais avec le temps, la communauté opte de plus en plus pour des versions minimalistes de chaque image. Autrefois avec une version slim de Debian, et aujourd’hui souvent avec une version Alpine Linux qui permet aux images de faire moins de 10Mo de base ! Préfère-donc utiliser les tags -alpine de chaque image.

Enfin, la gestion des permissions est déroutante au début. Docker tourne en root par défaut et a de bonnes raisons de le faire : autant dire que j’apprécie de pouvoir ouvrir les ports 80 ou 443 en dev comme en prod 🙂

Pour mieux comprendre pourquoi et comment bien gérer ses permissions : voir cet article.

Par où commencer?

En prenant le temps de lire la doc dans l’ordre, tout ne peut que bien se passer :

  1. Comprends Docker
  2. Comprends docker compose pour simplement faire cohabiter les services entre eux
  3. Installe docker et son plugin compose
  4. Ajoute ton user au groupe docker pour piloter le daemon sans sudo
  5. C’est parti ! N’oublie pas de garder la configuration la plus simple possible.
Exemple

Une stack complète React/Rust/PostgreSQL pourrait être lancée avec la simple config suivante à la racine d’un projet :

yml
version: "3"
services:
  db:
    image: postgres:alpine
    environment:
      - POSTGRES_PASSWORD=yolo
    volumes:
      - ./db/data:/var/lib/postgresql/data
  backend:
    image: rust:slim
    environment:
      - PGHOST=db
      - PGDATABASE=monservice
      - PGUSER=root
      - PGPASSWORD=yolo
    volumes:
      - ./backend/:/home/backend
      - ./webapp/:/home/webapp:ro
    command: cargo run
    ports:
      - 4000:4000
  webapp:
    image: node:alpine
    environment:
      - BACKEND_URL=http://backend:4000/
    volumes:
      - ./webapp/:/home/webapp
    command: sh -c "yarn install && CI=true yarn start"
    ports:
      - 80:8080

docker compose up -d et c’est parti directement sur localhost ! 💻

Retour aux articles 👀