đ§âđ» Aller plus loin avec les Dev Containers đł
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