15 minute read

un développeur assis à son bureau @wildagsx

Cet article est la deuxiĂšme partie de l’article prĂ©cĂ©dent sur les Dev Containers, je vous conseille de lire le premier article đŸ§‘â€đŸ’» A la dĂ©couverte des Dev Containers 🐳 avant de lire celui-ci.

Suite Ă  la mise en bouche avec l’article prĂ©cĂ©dent concernant les Dev Containers, je vous propose de pousser un plus loin l’utilisation de ceux-ci. En effet, le premier exemple Ă©tait assez simple puisqu’il s’agissait d’utiliser les Dev Containers pour l’écriture d’articles de mon blog (celui mĂȘme que vous lisez 😉). Il y a bien des cas oĂč l’environnement de travail ne se rĂ©sume pas Ă  juste installer Ruby.

Pour illustrer les diffĂ©rentes façons de customiser un Dev Container je vais prendre comme objectif d’avoir SliDesk installĂ© dans mon environnement de travail.

Si vous ne connaissez pas SliDesk je vous laisse allez le dĂ©couvrir dans l’article đŸ–Œïž Slides as code avec SliDesk đŸ‘šâ€đŸ’»

😌 La solution simple : postcommand

On l’a vu dans l’article de dĂ©couverte, le fichier de paramĂ©trage a un champ postCreateCommand qui permet d’exĂ©cuter des commandes aprĂšs le lancement du container. Du coup on peut lancer l’installation de SliDesk en utilisant cette option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
	"name": "Jekyll",
	"image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye",
	"postCreateCommand": {
		"bundle": "bundle install",
		"db": [
			"sudo",
			"wget",
			"-O",
			"/usr/bin/slidesk",
			"https://github.com/slidesk/slidesk/releases/download/2.11.1/slidesk_linux-arm"
		]
	},
}

Le gros avantage de cette solution est qu’elle est simple et rapide à mettre en place.

Notez au passage les deux façons de déclarer une commande. La premiÚre ne crée pas de shell alors que la deuxiÚme oui.

Si c’est trĂšs simple Ă  mettre en place on peut y voir quelques inconvĂ©nients :

  • il faut lister l’ensemble des commandes Ă  exĂ©cuter pour installer les applications (mĂȘme si il est possible de crĂ©er un fichier sh pour ne pas avoir Ă  le faire dans le devcontainer.json),
  • si j’ai besoin de SliDesk dans un autre projet il faut que je duplique les lignes d’installation et donc que je le maintienne dans l’ensemble des projets,
  • enfin, Ă  chaque dĂ©marrage de mon container, l’installation est de nouveau effectuĂ©e.

đŸ§© La feature : la solution naturelle ?

On l’a vu dans l’article prĂ©cĂ©dent : les features ont le cĂŽtĂ© pratique d’ĂȘtre des extensions Ă  nos images dĂ©jĂ  prĂ©-packagĂ©es. Bien entendu, SliDesk n’a pas de feature au moment oĂč j’écris cet article, et c’est tant mieux on va crĂ©er ensemble notre premiere feature.

Pour faire une feature rien de plus simple (du moins en local 😅) :

  • crĂ©er un rĂ©pertoire du nom de la feature dans le rĂ©pertoire .devcontainer/
  • crĂ©er un fichier devcontainer-features.json
  • crĂ©er un fichier install.sh avec le code d’installation souhaitĂ©

Voyons le fichier install.sh :

1
2
3
4
5
6
7
8
#!/bin/sh
set -e

echo "Activating feature 'slidesk'"

wget -O /usr/bin/slidesk https://github.com/slidesk/slidesk/releases/download/2.11.1/slidesk_linux-${DISTRIBUTION}

chmod +x /usr/bin/slidesk

Vous constatez que je suis loin d’ĂȘtre un expert en bash 😅

Une fois ce fichier créé, il suffit de créer le fichier devcontainer-features.json :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "name": "SliDesk",
  "id": "slidesk",
  "version": "1.0.0",
  "description": "A feature to install SliDesk",
  "options": {
    "distribution": {
      "type": "string",
      "enum": [
        "arm",
        "amd"
      ],
      "default": "arm",
      "description": "Choose your distribution type: arm or amd."
    }
  }
}

Enfin, il faut ajouter la feature Ă  la liste des features dans le fichier .devcontainer/devcontainer.json :

1
2
3
4
5
6
7
8
9
10
{
	"name": "Jekyll",
	"image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye",
	"features": {
		"./slidesk": {}
	},
	"postCreateCommand": {
		"bundle": "bundle install"
	},
}

Notez la syntaxe spĂ©ciale de l’activation de feature car elle se trouve dans le mĂȘme rĂ©pertoire que le fichier devcontainer.json, Ă  savoir .devcontainer.

Bien entendu, ici, nous n’avons fait que dĂ©porter le code d’installation du fichier .devcontainer.json vers la feature. Cela apporte tout de mĂȘme plus de libertĂ© sur le code d’installation et le versioning. Cependant, pour ne plus avoir Ă  dupliquer le code en lui-mĂȘme dans l’ensemble des projets, il faut packager la feature et la distribuer via une registry.

📩 Release et distribution de la feature

Il faut avouer que c’est plutĂŽt bien fait pour packager et releaser une feature. La premiĂšre chose Ă  faire va ĂȘtre de faire un fork du repository de template : https://github.com/devcontainers/feature-starter.

Ensuite, il suffit de copier le répertoire slidesk précédemment créé localement dans un répertoire src. Pour voir ce que cela donne vous pouvez allez voir ici : https://github.com/philippart-s/feature-slidesk.

Notez que pour développer une feature, le template vous met à disposition un environnement de développement complet grùce aux Dev Containers bien sûr !

Et lĂ  oĂč c’est cool c’est que tout est fournit pour faire une release grĂące Ă  la GitHub action dans le rĂ©pertoire .github de votre repository fraĂźchement crĂ©Ă©. De plus, c’est un workflow qui peut se dĂ©clencher manuellement.

Une fois la release effectuée votre feature est disponible dans votre registry de votre organisation personnelle, dans mon cas : https://ghcr.io/philippart-s.

La feature est donc disponible avec l’url https://ghcr.io/philippart-s/feature-slidesk/slidesk:latest.

Voyons maintenant notre fichier devcontainer.json :

1
2
3
4
5
6
7
8
9
10
{
	"name": "Jekyll",
	"image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye",
	"features": {
		"ghcr.io/philippart-s/feature-slidesk/slidesk:1.0.0": {}
	},
	"postCreateCommand": {
		"bundle": "bundle install"
	}
}

Avec ce mode d’installation et de distribution on a gommĂ© nos problĂšme de rĂ©pĂ©tition de code et de lourdeur dĂ©clarative dans le fichier devconainer.json.

Et si on allait plus loin ? Si on faisait son propre template ? Imaginez que SliDesk soit un de mes logiciels essentiels et que je doive l’activer Ă  chaque fois dans mes configurations. Cela a beau n’ĂȘtre que quelques lignes, je devrais les rĂ©pĂ©ter tout de mĂȘme.

L’ensemble du code de la feature est disponible dans le repository feature-slidesk

🐳 L’image custom : la solution ultime ?

Nous l’avons vu prĂ©cĂ©demment, il est possible de rajouter des Ă©lĂ©ments Ă  votre environnement via les features ou les commands. Les deux fonctionnent et ont leurs avantages et leurs inconvĂ©nients. Le problĂšme qu’elles partagent est que, si vous voulez toujours avoir SliDesk (dans mon exemple), il faut rĂ©pĂ©ter l’ajout de la configuration.
Et si, SliDesk Ă©tait dĂ©jĂ  prĂ©sent au moment de lancer le Dev Container ? Vous me voyez venir et avez certainement devinĂ© : cela va ĂȘtre possible avec l’attribut image du fichier de configuration. Oui, il est possible de crĂ©er sa propre image Docker pour lancer le Dev Container avec.

En dehors de fabriquer une image qui va correspondre Ă  100% Ă  vos besoins, l’autre avantage va ĂȘtre que vous allez aussi pouvoir optimiser cette image. On l’a vu dans l’article prĂ©cĂ©dent les images peuvent assez vite grossir et peut-ĂȘtre que vous n’avez pas besoin de tout ce que propose une image basĂ©e sur Ubuntu par exemple.

Tout comme pour les features, il est possible de déclarer son image localement, pour cela il va vous suffire de créer un fichier Dockerfile dans le répertoire .devcontainer de votre projet.

Prenons l’exemple, trùs simple suivant :

1
2
3
4
FROM mcr.microsoft.com/devcontainers/jekyll:2-bullseye

ADD install-slidesk.sh .
RUN ./install-slidesk.sh

Nous de discuterons pas de l’optimisation de l’image ici, l’idĂ©e est de bĂ©nĂ©ficier de ce qui existe dĂ©jĂ  dans l’image jekyll en lui ajoutant l’installation de SliDesk.

Ensuite dans le fichier devcontainer.json il faut dĂ©clarer l’image Ă  utiliser en replaçant l’option image par un attribut build.

1
2
3
4
5
6
7
8
9
{
	"name": "Jekyll",
	"build": {
		"dockerfile": "Dockerfile"
	},
	"postCreateCommand": {
		"bundle": "bundle install",
	}
}

Avec cette approche vous avez la possibilitĂ© de construire votre image spĂ©cifique qui va rĂ©pondre 100% Ă  vos besoins. Dans une approche Open Source ou de partage de configuration Ă  d’autres personnes c’est trĂšs pratique. Dans le cas, oĂč comme moi, on souhaite rĂ©utiliser une configuration entre plusieurs projets je vous conseille de construire l’image, la mettre sur une registry puis la rĂ©fĂ©rencer dans votre devcontainer.json.

🐳 🐋 Docker in Docker

Ce que je viens de vous proposer ne semble pas une tñche insurmontable n’est-ce pas ? Souvenez-vous : nous sommes dans un container et je vous propose de fabriquer une image.

Inception ? Non Docker in Docker !

Docker in Docker (DinD de son petit nom) est quelque chose de recherchĂ© par grand nombre de dĂ©veloppeuses et de dĂ©veloppeurs depuis longtemps. Il y a beaucoup d’articles de blogs qui expliquent comment faire et les risques inhĂ©rents Ă  cette pratique. Je vous laisse donc aller les consulter. Il y a mĂȘme une image officielle de Docker permettant de le faire, avec pas mal de doc qui explique le concept.

Retenons juste la bone nouvelle : c’est possible 😉.

L’autre bonne nouvelle est qu’une feature existe, allons-y et rajoutons cette feature pour fabriquer notre image :

1
2
3
"features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
}

Passons Ă  la construction de notre image et au push de celle-ci : docker build -t wilda/jekyll:1.0.0 . && docker push wilda/jekyll:1.0.0

Ensuite vous pouvez l’utiliser comme image dans votre devcontainer.json :

1
2
3
4
5
6
7
{
	"name": "Jekyll",
	"image": "wilda/jekyll:1.0.0",
	"postCreateCommand": {
		"bundle": "bundle install",
	},
}

A ce stade j’arrive Ă  quelque chose de trĂšs agrĂ©able : j’ai une image type, dans mon cas Jekyll, que je vais pouvoir utiliser dans tous mes devs Jekyll sans avoir Ă  refaire la configuration de mon IDE.

⚠ N’oubliez pas que le Dockerfile en clair reste une bonne approche si l’objectif est d’avoir quelque chose de trĂšs spĂ©cifique pour un projet et qui n’est pas couvert simplement par des features ou des commandes. ⚠

Si vous utilisez Docker rĂ©guliĂšrement vous vous demandez peut-ĂȘtre : et Docker Compose ?

🐳 đŸ—ïž Docker Compose : l’ami complexe ?

Alors oui Docker Compose peut ĂȘtre utilisĂ©. Pour moi il rĂ©pond Ă  un besoin bien spĂ©cifique : construire un environment de dĂ©veloppement complexe qui ne se limite pas Ă  l’environnement de dĂ©veloppement mais Ă  diffĂ©rentes briques d’architectures qu’aurait besoin l’application.

En effet, jusqu’à prĂ©sent mon but Ă©tait d’offrir le meilleur environnement de dĂ©veloppement possible, reproductible et sans installation sur ma machine. Et si j’englobais aussi les ressources plus complexes comme par exemple une base de donnĂ©es pour une application. L’idĂ©e est simple : pouvoir tester l’application sans devoir crĂ©er des mocks ou ne pas ĂȘtre iso production. Exactement comme le font les test containers au final.

Les test containers sont eux aussi dans ma TODO list, et feront certainement l’objet d’un futur article 😉.

Nous verrons certainement dans un autre article une configuration plus poussĂ©e mais pour l’exemple je vais prendre le cas oĂč je veux installer une base de donnĂ©es PostgreSQL qui ne serait pas dans mon image principale. Les besoins peuvent ĂȘtre multiples mais le premier Ă©vident va ĂȘtre le partage et la rĂ©utilisation d’images entre diffĂ©rents types de dĂ©veloppements. Par exemple, pour ma base de donnĂ©es je peux trĂšs bien l’utiliser dans un dĂ©veloppement de type Java et JavaScript. Ce serait dommage de dupliquer le code d’installation lĂ  oĂč on pourrait le mutualiser.

Pour avoir une base de donnĂ©es dans un container diffĂ©rent de mon environnement de dĂ©veloppement il faut donc que je crĂ©e un fichier docker-compose.yml au mĂȘme niveau que mon devcontainer.json.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.8'
services:
  devcontainer:
    image: wilda/jekyll:1.0.0
    volumes:
      - ../..:/workspaces:cached
    command: sleep infinity
    network_mode: service:db
  db:
    image: postgres:latest
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres

â„č La base de donnĂ©es est visible depuis le service devcontainer grĂące Ă  : network_mode: service:db

Puis, le référencer dans le fichier devcontainer.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
	"name": "Jekyll",

	"dockerComposeFile": "docker-compose.yml",
	"service": "devcontainer",
	"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
	
	"features": {
		"ghcr.io/robbert229/devcontainer-features/postgresql-client:1": {}
	},
	
	"postCreateCommand": {
		"bundle": "bundle install",
	},
}

Notez ici la nĂ©cessitĂ© d’indiquer deux choses :

  • quel service du Docker Compose fait office de container de travail, reprĂ©sentĂ© ici par le service devcontainer
  • le rĂ©pertoire de travail (aka workspace), c’est transparent lorsque vous utilisez des images toutes faites mais lĂ  il faut l’indiquer 😉

En plus de notre environnement de travail gĂ©rĂ© par le service devcontainer il est aussi possible d’utiliser simplement la base de donnĂ©es PostgreSQL :

1
2
3
4
5
6
7
8
$ psql -h localhost -U postgres -d postgres
psql (13.18 (Debian 13.18-1.pgdg110+1), server 17.2 (Debian 17.2-1.pgdg120+1))
WARNING: psql major version 13, server major version 17.
         Some psql features might not work.
Type "help" for help.

postgres=# 

MĂȘme si l’utilisation Docker Compose sort un peu de mon use case avec SliDesk cela me semblait pertinent de le prĂ©senter dans cet article comme alternative pour la construction d’un environnement de dĂ©veloppement complet, autonome et reproductible.

đŸ’« Le template : factoriser de la configuration

On a vu plusieurs moyens de configurer l’installation de SliDesk dans notre environnement. Que ce soit avec le postCreateCommand ou la feature il faut positionner du paramĂ©trage dans le fichier .devcontainer.json et donc dupliquer cette configuration pour les projets de mĂȘme type. Il existe une notion vous permettant de dĂ©marrer un projet avec le fichier devcontainer.json dĂ©jĂ  prĂ©-renseignĂ© des valeurs nĂ©cessaires. Cette fonctionnalitĂ© s’appelle un template. Ce sont des templates que vous manipulez avec VSCode lors de l’initialisation d’un Dev Container.

Imaginons que je veuille créer un template pour avoir un environnement permettant de faire du Jekyll tout en ayant le client SliDesk.

Pour cela, je vous conseille de partir du template d’exemple qui non seulement a le code et paramĂ©trage d’exemple mais aussi des GitHub action vous permettant de packager et mettre Ă  disposition votre template.

Une fois forkĂ© vous allez voir que vous avez un rĂ©pertoire .devcontainer qui va vous permettre de rĂ©ouvrir le projet avec un Dev Container vous permettant d’avoir tous les outils nĂ©cessaires pour dĂ©velopper votre template 😉.

Ensuite commence le développement de votre template. Tout se passe dans le répertoire src. A la racine il y a un répertoire .devcontainer et un fichier devcontainer-template.json.

Voyons ce que contient le rĂ©pertoire .devcontainer : un fichier devcontainer.json 😅. Eh oui c’est ce fichier qui sera utilisĂ© pour crĂ©er le fichier devcontainer.json lorsque le template sera utilisĂ©.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
	"name": "Jekyll with SliDesk",
	"image": "mcr.microsoft.com/devcontainers/jekyll:2-${templateOption:imageVariant}",

	// 👇 Features to add to the Dev Container. More info: https://containers.dev/implementors/features.
	 "features": {
		"ghcr.io/philippart-s/feature-slidesk/slidesk:1.0.0": {}
	 },

	// 👇 Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	"postCreateCommand": {
		"bundle": "bundle install",
		"sliDesk": "slidesk --version"
	}
	// 👇 Configure tool-specific properties.
	// "customizations": {},

	// 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
	// "remoteUser": "root"
}

On le voit ici le fichier ressemble beaucoup aux autres fichiers devcontainer.json que l’on a dĂ©jĂ  crĂ©Ă©. Les seules diffĂ©rences sont que l’on va pouvoir apporter un peu de gĂ©nĂ©ricitĂ©, par exemple ici on veut pouvoir choisir le type de distribution Debian avec le paramĂštre ${templateOption:imageVariant}. Sinon vous constatez que cela ressemble beaucoup Ă  ce que l’on avait crĂ©Ă© dans le prĂ©cĂ©dent article.

Un fois ce fichier crĂ©Ă© il ne reste plus qu’à dĂ©clarer le template avec le fichier .devcontainer-templates.json qui se trouve Ă  la racine de src.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "id": "slidesk",
    "version": "1.0.0",
    "name": "SliDesk with Dev Containers",
    "description": "A Template to have SliDesk in your Dev Containers",
    "documentationURL": "https://github.com/philippart-s/template-slidesk/tree/main/src/slidesk",
    "licenseURL": "https://github.com/philippart-s/template-slidesk/blob/main/LICENSE",
    "options": {
        "imageVariant": {
            "type": "string",
            "description": "Debian version (use bullseye on local arm64/Apple Silicon):",
            "proposals": [
                "bullseye",
                "buster"
            ],
            "default": "bullseye"
        }
    },
    "platforms": [
        "Any"
    ]
}

On donne quelques informations utiles pour la suite comme le nom et l’identifiant du template. Surtout on indique les valeurs possibles pour la variable ${templateOption:imageVariant} et sa valeur par dĂ©faut.

Une fois que tout ça est push et sur GitHub il ne reste plus qu’à packager le template avec la GitHub Action manuelle Release Dev Container Templates & Generate Documentation prĂ©sente dans le fichier .github/workflows/release.yaml.

Cela met Ă  disposition le template dans votre section package de votre organisation GitHub personnelle, par exemple : https://github.com/philippart-s?tab=packages&repo_name=template-slidesk

Ensuite votre template est utilisable, de la mĂȘme maniĂšre que pour l’article prĂ©cĂ©dent avec VSCcode : CMD+SHIFT+P puis Dev Containers: Add Dev Containers configuration Files.... Sauf qu’ici au lieu de choisir dans la liste il faut rentrer votre image : ghcr.io/philippart-s/template-slidesk/slidesk:latest.

â„č Il est tout Ă  fait possible de rajouter le template dans la liste officielle qui permet qu’il apparaisse directement dans la liste des templates. Je ne l’ai pas fait dans cet exemple Ă©tant donnĂ© que le template n’est que pour moi.

Une fois que vous utilisez le template, un projet est créé avec un devcontainer.json pré-configuré.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
	"name": "Jekyll with SliDesk",
	"image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye",

	// 👇 Features to add to the Dev Container. More info: https://containers.dev/implementors/features.
	 "features": {
		"ghcr.io/philippart-s/feature-slidesk/slidesk:1.0.0": {}
	 },

	// 👇 Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	"postCreateCommand": {
		"bundle": "bundle install",
		"sliDesk": "slidesk --version"
	}
	// 👇 Configure tool-specific properties.
	// "customizations": {},

	// 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
	// "remoteUser": "root"
}

L’ensemble du code du template se trouve dans le repository template-slidesk

En conclusion

On vient de le voir : on peut aller beaucoup plus loin avec les Dev Containers. A vous de voir si vous souhaitez simplement les utiliser en mode one shot dans un projet, vous faire toute une bibliothĂšque d’environnements de dĂ©veloppements. Vous pouvez aussi mettre Ă  disposition pour les autres via des images, des features ou encore des templates. Bref laisser libre cour Ă  votre imagination tout en ayant quelque chose de portable et reproductible.

Pour finir, vous l’avez certainement devinĂ© mais dans notre cas de SliDesk une feature serait le plus adaptĂ© 😉.

Dans les semaines Ă  venir, je vais devoir crĂ©er de nombreuses dĂ©mos avec des technologies trĂšs hĂ©tĂ©rogĂšnes. Nul doute que j’aurai des choses Ă  partager autour des Dev Containers 😉.

Si vous ĂȘtes arrivĂ©s jusque lĂ  merci de m’avoir lu et si il y a des coquilles n’hĂ©sitez pas Ă  me faire une issue ou PR 😊.

Merci à ma relectrice, Fanny, qui vous permet de lire cet article sans avoir trop les yeux qui saignent 😘.

Comments