{"id":7318,"date":"2026-09-15T06:00:41","date_gmt":"2026-09-15T05:00:41","guid":{"rendered":"https:\/\/www.blaess.fr\/christophe\/?p=7318"},"modified":"2026-09-12T17:34:26","modified_gmt":"2026-09-12T16:34:26","slug":"developper-du-code-personnalise-pour-eris-linux","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2026\/09\/15\/developper-du-code-personnalise-pour-eris-linux\/","title":{"rendered":"D\u00e9velopper du code personnalis\u00e9 pour Eris Linux"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"alignright size-full is-resized\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/09\/thumbnail-04-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/09\/thumbnail-04-2.png\" alt=\"\" class=\"wp-image-7831\" style=\"aspect-ratio:1.4992722645620937;width:311px;height:auto\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/09\/thumbnail-04-2.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/09\/thumbnail-04-2-300x225.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/09\/thumbnail-04-2-768x576.png 768w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Dans les articles pr\u00e9c\u00e9dents de cette s\u00e9rie consacr\u00e9e \u00e0 <strong>Eris Linux<\/strong>, nous avons commenc\u00e9 par examiner <a href=\"https:\/\/www.blaess.fr\/christophe\/2026\/08\/25\/eris-linux\/\" data-type=\"post\" data-id=\"7123\"><strong>les principes et les avantages d&rsquo;Eris<\/strong><\/a>. Nous avons ensuite \u00e9tudi\u00e9 <a href=\"https:\/\/www.blaess.fr\/christophe\/2026\/09\/01\/installer-eris-linux-sur-un-raspberry-pi-5\/\" data-type=\"post\" data-id=\"7066\"><strong>comment installer Eris Linux<\/strong><\/a> sur une carte standard (un Raspberry Pi 5) puis <strong><a href=\"https:\/\/www.blaess.fr\/christophe\/2026\/09\/08\/deployer-des-containers-sur-eris-linux\/\" data-type=\"post\" data-id=\"7232\">comment d\u00e9ployer depuis le <em>Device Manager<\/em> des containers<\/a><\/strong> de test sur notre \u00e9quipement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous allons voir dans cet article comment <strong>d\u00e9velopper du code embarqu\u00e9 dans un container<\/strong>, et comment le d\u00e9ployer sur notre carte cible.<\/p>\n\n\n\n<!--more-->\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p class=\"has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background wp-block-paragraph\"><strong>Au programme&nbsp;:<\/strong> services Linux, acc\u00e8s mat\u00e9riel, C\/C++, Qt, Rust, Python et g\u00e9n\u00e9ration multiarchitecture avec Docker Buildx<\/p>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Pour comprendre comment cr\u00e9er du code pour Eris Linux, je vous propose de nous appuyer sur les exemples contenus dans le <em>repository<\/em> Git suivant&nbsp;: <strong><a href=\"https:\/\/github.com\/Eris-Linux\/Eris-Linux-Containers\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/Eris-Linux\/Eris-Linux-Containers<\/a><\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;~]$ <strong>git  clone  https:\/\/github.com\/Eris-Linux\/Eris-Linux-Containers<\/strong>\nCloning into 'Eris-Linux-Containers'...\nremote: Enumerating objects: 183, done.\nremote: Counting objects: 100% (183\/183), done.\nremote: Compressing objects: 100% (118\/118), done.\nremote: Total 183 (delta 79), reused 164 (delta 60), pack-reused 0 (from 0)\nReceiving objects: 100% (183\/183), 95.24 KiB | 2.51 MiB\/s, done.\nResolving deltas: 100% (79\/79), done.\n\n&#91;~]$ <strong>cd  Eris-Linux-Containers\/<\/strong><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La version <code>1.0.0<\/code> de ce repository contient 11 exemples successifs, couvrant un maximum de besoins classiques pour les syst\u00e8mes embarqu\u00e9s&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mise en \u0153uvre de <strong>services classiques de Linux<\/strong> (serveurs SSH et NGINX) avec des configurations personnalis\u00e9es&nbsp;: <code>01-ssh-server<\/code> et <code>02-api-test-web<\/code> ;<\/li>\n\n\n\n<li><strong>Scripts shell<\/strong> acc\u00e9dant \u00e0 des ressources mat\u00e9rielles&nbsp;: <code>03-led-trigger-setup<\/code> et <code>04-turns-leds-off<\/code> ;<\/li>\n\n\n\n<li><strong>Programme C compil\u00e9<\/strong> avec \u00e9ventuellement acc\u00e8s \u00e0 la biblioth\u00e8que d&rsquo;API d&rsquo;Eris Linux&nbsp;: <code>05-hello-world-in-c<\/code> et <code>06-api-test-tcp<\/code> ;<\/li>\n\n\n\n<li>Application graphique utilisant la <strong>biblioth\u00e8que Qt<\/strong> : <code>07-api-test-graphical<\/code> ;<\/li>\n\n\n\n<li><strong>Programme Rust<\/strong> compil\u00e9, avec diff\u00e9rents types de containers&nbsp;: <code>08-hello-world-in-rust<\/code>, <code>09-hello-rust-distroless<\/code> et <code>10-hello-rust-minimal<\/code> ;<\/li>\n\n\n\n<li><strong>Application en Python<\/strong> : <code>11-hello-world-in-python<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Les premiers exemples correspondent aux containers de d\u00e9monstration que nous avons vus dans l&rsquo;article pr\u00e9c\u00e9dent, mais des exemples suppl\u00e9mentaires sont pr\u00e9sents. Il est probable que d&rsquo;autres exemples seront ajout\u00e9s dans l&rsquo;avenir et je compl\u00e9terai cet article au besoin.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Table des mati\u00e8res<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"#T1\">1 &#8211; Installation de services Linux<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T1.1\">1.1 &#8211; Serveur SSH<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T1.1.1\">1.1.1 &#8211; Pr\u00e9paration de l&rsquo;image<\/a><\/li>\n\n\n\n<li><a href=\"#T1.1.2\">1.1.2 &#8211; Enregistrement de l&rsquo;image<\/a><\/li>\n\n\n\n<li><a href=\"#T1.1.3\">1.1.3 &#8211; D\u00e9ploiement du container<\/a><\/li>\n\n\n\n<li><a href=\"#T1.1.4\">1.1.4 &#8211; V\u00e9rification du fonctionnement<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#T1.2\">1.2 &#8211; Serveur Web avec configuration et pages HTML personnalis\u00e9es<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#T2\">2 &#8211; Scripts shell acc\u00e9dant au mat\u00e9riel<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T2.1\">2.1 &#8211; Acc\u00e8s aux GPIO<\/a><\/li>\n\n\n\n<li><a href=\"#T2.2\">2.2 &#8211; Configuration des triggers de LEDs<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T2.2.1\">2.2.1 &#8211; Principe<\/a><\/li>\n\n\n\n<li><a href=\"#T2.2.2\">2.2.2 &#8211; Script et Dockerfile<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#T3\" data-type=\"internal\" data-id=\"#T1\">3 &#8211; Cross-compilation<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T3.1\">3.1 &#8211; Docker BuildX<\/a><\/li>\n\n\n\n<li><a href=\"#T3.2\">3.2 &#8211; Pr\u00e9requis<\/a><\/li>\n\n\n\n<li><a href=\"#T3.3\">3.3 &#8211; Programme compil\u00e9 en C<\/a><\/li>\n\n\n\n<li><a href=\"#T3.4\">3.4 &#8211; Programme C utilisant le r\u00e9seau<\/a><\/li>\n\n\n\n<li><a href=\"#T3.5\">3.5 &#8211; Programme C appelant l&rsquo;API d&rsquo;Eris Linux<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#T4\">4 &#8211; Programmes en Rust<\/a>\n<ul class=\"wp-block-list\">\n<li><a href=\"#T4.1\">4.1 &#8211; Avertissement<\/a><\/li>\n\n\n\n<li><a href=\"#T4.2\">4.2 &#8211; Compilation d&rsquo;un programme Rust<\/a><\/li>\n\n\n\n<li><a href=\"#T4.3\">4.3 &#8211; Image Distroless pour le container d&rsquo;ex\u00e9cution<\/a><\/li>\n\n\n\n<li><a href=\"#T4.4\">4.4 &#8211; Container avec application unique<\/a><\/li>\n\n\n\n<li><a href=\"#T4.5\">4.5 &#8211; Comparaisons<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><a href=\"#T5\">5 &#8211; Exemple en Python<\/a><\/li>\n\n\n\n<li><a href=\"#T6\">Conclusion<\/a><\/li>\n<\/ul>\n\n\n\n<h1 id=\"T1\" class=\"wp-block-heading\">1 &#8211; Installation de services Linux<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"installation-ssh\">Nous allons commencer par l&rsquo;installation d&rsquo;un <strong>simple service classique pr\u00e9d\u00e9fini<\/strong> ne n\u00e9cessitant pas d&rsquo;ajout de code suppl\u00e9mentaire. Nous allons examiner la production de l&rsquo;image, son enregistrement sur le serveur Eris Linux et son d\u00e9ploiement sur des cartes cibles. Une bonne partie de ces \u00e9tapes se retrouvera \u00e0 l&rsquo;identique pour les autres exemples.<\/p>\n\n\n\n<h2 id=\"T1.1\" class=\"wp-block-heading\">1.1 &#8211; Serveur SSH<\/h2>\n\n\n\n<h3 id=\"T1.1.1\" class=\"wp-block-heading\">1.1.1 &#8211; Pr\u00e9paration de l&rsquo;image<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Dans le dossier <code><strong>01-ssh-server<\/strong><\/code> se trouve un simple fichier <code><strong>Dockerfile<\/strong><\/code> (accompagn\u00e9 bien s\u00fbr du <code>README.md<\/code> habituel) qui installe dans l&rsquo;image de container un serveur <code>openssh<\/code>, g\u00e9n\u00e8re une cl\u00e9 pour le serveur, et affine son fichier de configuration. Ce m\u00eame <code>Dockerfile<\/code> cr\u00e9e un compte utilisateur nomm\u00e9 <code>eris<\/code> avec le mot de passe <code>linux<\/code> et fixe le mot de passe <code>root<\/code> pour l&rsquo;utilisateur <code>root<\/code> du container. En voici une version l\u00e9g\u00e8rement simplifi\u00e9e, je vous encourage \u00e0 consulter le fichier d&rsquo;origine.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\nFROM alpine:latest\n\nRUN apk update                                                \\\n &amp;&amp; apk add --no-cache openssh                                \\\n &amp;&amp; ssh-keygen -A                                             \\\n &amp;&amp; echo \"PermitRootLogin yes\" &gt;&gt; \/etc\/ssh\/sshd_config        \\\n &amp;&amp; echo \"PasswordAuthentication yes\" &gt;&gt; \/etc\/ssh\/sshd_config \\\n &amp;&amp; adduser -D eris                                           \\\n &amp;&amp; echo \"root:root\"  | chpasswd                              \\\n &amp;&amp; echo \"eris:linux\" | chpasswd\n\nEXPOSE 22\n\nCMD &#91; \"\/usr\/sbin\/sshd\", \"-D\" ]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La simplicit\u00e9 de ce <code>Dockerfile<\/code> vous permettra de l&rsquo;adapter \u00e0 vos besoins pour installer tout autre service Linux. <strong>Pour cr\u00e9er le container, il suffit d&rsquo;appeler le script pr\u00e9vu<\/strong> \u00e0 cet effet en suivant la ligne de commande indiqu\u00e9e dans le fichier <code>README.md<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls<\/strong>\n01-ssh-server         04-turn-leds-off     07-api-test-graphical     10-hello-rust-minimal     README.md\n02-api-test-web       05-hello-world-in-c  08-hello-world-in-rust    11-hello-world-in-python  create-container\n03-led-trigger-setup  06-api-test-tcp      09-hello-rust-distroless  LICENSE\n\n$ <strong>.\/create-container  arm64  .\/01-ssh-server  ssh-server<\/strong>\n &#91;...]\n#5 5.150 chpasswd: password for 'root' changed\n#5 5.190 chpasswd: password for 'eris' changed\n#5 DONE 5.2s\n\n#6 exporting to client tarball\n#6 sending tarball 0.1s done\n#6 DONE 0.1s\n\/home\/cpb\/Eris-Linux-Containers\n\n$ <strong>ls<\/strong>\n01-ssh-server         04-turn-leds-off     07-api-test-graphical     10-hello-rust-minimal     README.md\n02-api-test-web       05-hello-world-in-c  08-hello-world-in-rust    11-hello-world-in-python  create-container\n03-led-trigger-setup  06-api-test-tcp      09-hello-rust-distroless  LICENSE                   <strong>ssh-server.tar.bz2<\/strong>\n\n$ \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nous voyons qu&rsquo;un fichier <code>ssh-server.tar.bz2<\/code> est apparu, c&rsquo;est l&rsquo;image du container que nous allons ensuite envoyer vers le <em>Device Manager<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 id=\"T1.1.2\" class=\"wp-block-heading\">1.1.2 &#8211; Enregistrement de l&rsquo;image<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;image au format <code>.tar.bz2<\/code> que nous avons produite peut \u00eatre stock\u00e9e sur le serveur d&rsquo;Eris Linux puis d\u00e9ploy\u00e9e vers un groupe d&rsquo;\u00e9quipements. Pour cela, il faut se connecter sur le <em>Device Manager<\/em> \u00e0 l&rsquo;adresse <a href=\"https:\/\/www.eris-linux.net\/\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/www.eris-linux.net\/<\/a> puis se rendre sur l&rsquo;onglet \u00ab\u00a0<em><strong>My containers<\/strong><\/em>\u00a0\u00bb et cliquer sur le bouton \u00ab\u00a0<em><strong>Upload a container<\/strong><\/em>\u00a0\u00bb .<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"518\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01-1024x518.png\" alt=\"\" class=\"wp-image-7348\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01-1024x518.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01-300x152.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01-768x389.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-01.png 1279w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 1 &#8211; <em>Onglet \u00ab\u00a0My containers\u00a0\u00bb<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Dans la fen\u00eatre qui s&rsquo;ouvre, cliquez sur le bouton \u00ab\u00a0<em><strong>Browse<\/strong><\/em>\u00a0\u00bb et naviguez jusqu&rsquo;au fichier <code>ssh-server.tar.bz2<\/code> que nous venons de produire.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-02.png\"><img loading=\"lazy\" decoding=\"async\" width=\"685\" height=\"458\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-02.png\" alt=\"\" class=\"wp-image-7349\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-02.png 685w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-02-300x201.png 300w\" sizes=\"auto, (max-width: 685px) 100vw, 685px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 2 &#8211; <em>T\u00e9l\u00e9chargement d&rsquo;un fichier image de container.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Vous pouvez choisir de <strong>prot\u00e9ger votre image de container par un mot de passe<\/strong>. Le fichier sera alors chiffr\u00e9 avant d&rsquo;\u00eatre enregistr\u00e9 sur le serveur Eris Linux. Le mot de passe ne sera pas stock\u00e9. Lorsque vous choisirez d&rsquo;installer cette image de container sur un groupe d&rsquo;\u00e9quipements il faudra re-saisir le mot de passe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une fois le fichier d&rsquo;image s\u00e9lectionn\u00e9, cliquez sur \u00ab\u00a0<strong><em>Upload<\/em><\/strong>\u00a0\u00bb .<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"514\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03-1024x514.png\" alt=\"\" class=\"wp-image-7350\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03-1024x514.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03-300x151.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03-768x386.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-03.png 1279w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 3 &#8211; <em>Fichier d&rsquo;image t\u00e9l\u00e9charg\u00e9.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">De retour dans l&rsquo;onglet \u00ab\u00a0<em><strong>My containers<\/strong><\/em>\u00a0\u00bb s\u00e9lectionnez votre container nouvellement charg\u00e9 et cliquez sur \u00ab\u00a0<em><strong>Setup<\/strong><\/em>\u00a0\u00bb pour le configurer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remplissez les champs&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u00ab\u00a0<em><strong>Container name<\/strong><\/em>\u00a0\u00bb : Choisissez un nom explicite qu&rsquo;il vous sera facile d&rsquo;identifier. Pendant la mise au point d&rsquo;un projet embarqu\u00e9, le nombre de containers de test augmente rapidement, il est important de les reconna\u00eetre facilement.<\/li>\n\n\n\n<li>\u00ab\u00a0<strong>Container version<\/strong>\u00a0\u00bb : Remplir et maintenir ce champ vous permet de conserver plusieurs versions du m\u00eame container au cours des essais successifs.<\/li>\n\n\n\n<li>\u00ab\u00a0<em><strong>Compatible boards<\/strong><\/em>\u00a0\u00bb : Un container est compil\u00e9 pour une carte donn\u00e9e. Si votre container est pr\u00e9vu pour une gamme de diff\u00e9rentes cartes, t\u00e9l\u00e9chargez autant d&rsquo;exemplaires que de types de cartes concern\u00e9es et jouez sur \u00ab\u00a0<em>Container name<\/em>\u00a0\u00bb pour les distinguer.<\/li>\n\n\n\n<li>\u00ab\u00a0<em><strong>Graphical display<\/strong><\/em>\u00a0\u00bb : Si votre container est pr\u00e9vu pour fonctionner sur des cartes disposant d&rsquo;\u00e9crans graphiques, cochez cette case.<\/li>\n\n\n\n<li>\u00ab\u00a0<em><strong>Privileged container<\/strong><\/em>\u00a0\u00bb : Un container privil\u00e9gi\u00e9 a acc\u00e8s \u00e0 toutes les entr\u00e9es des r\u00e9pertoires <code>\/dev<\/code> et <code>\/sys<\/code> de votre \u00e9quipement. Il peut ainsi communiquer directement avec le mat\u00e9riel sans passer par l&rsquo;API Eris Linux. Nous en verrons un exemple ci-dessous.<\/li>\n\n\n\n<li>\u00ab\u00a0<em><strong>Exported ports<\/strong><\/em>\u00a0\u00bb : Si votre image propose des connexions r\u00e9seau depuis l&rsquo;ext\u00e9rieur du container, indiquez-les ici sous la forme \u00ab\u00a0<code>&lt;ext>:&lt;int>\/&lt;proto><\/code>\u00a0\u00bb avec <code>&lt;ext><\/code> le num\u00e9ro du port \u00e0 utiliser \u00e0 l&rsquo;ext\u00e9rieur du container, <code>&lt;int><\/code> le num\u00e9ro du port interne au container et <code>&lt;proto><\/code> le protocole (<code>tcp<\/code> ou <code>udp<\/code>, avec <code>tcp<\/code> par d\u00e9faut).<\/li>\n\n\n\n<li>\u00ab\u00a0<em><strong>Comments<\/strong>\u00ab\u00a0<\/em> : un champ de commentaires libres concernant l&rsquo;image de container.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Pour le champ \u00ab\u00a0<em>Exported ports<\/em>\u00a0\u00bb j&rsquo;ai indiqu\u00e9 ici \u00ab\u00a0<code><strong>12345:22\/tcp<\/strong><\/code>\u00a0\u00bb ainsi lorsqu&rsquo;on se connectera sur le port TCP <code>12345<\/code> de l&rsquo;\u00e9quipement, la communication sera \u00e9tablie avec le port <code>22<\/code> \u00e0 l&rsquo;int\u00e9rieur du container (le serveur SSH).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une fois la fen\u00eatre de \u00ab\u00a0<em>Setup<\/em>\u00a0\u00bb referm\u00e9e, les informations sont affich\u00e9es pour rappel.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"486\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1-1024x486.png\" alt=\"\" class=\"wp-image-7389\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1-1024x486.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1-300x142.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1-768x364.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-05-1.png 1279w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 5 &#8211; <em>Configuration de l&rsquo;image de container.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<h3 id=\"T1.1.3\" class=\"wp-block-heading\">1.1.3 &#8211; D\u00e9ploiement du container<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le d\u00e9ploiement du container sur un groupe d&rsquo;\u00e9quipements se d\u00e9roule comme pour les containers de d\u00e9monstrations de l&rsquo;article pr\u00e9c\u00e9dent. On se rend sur l&rsquo;onglet \u00ab\u00a0<em><strong>My devices<\/strong><\/em>\u00a0\u00bb et on clique sur un <em>slot<\/em> libre du groupe qui nous int\u00e9resse.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"624\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06-1024x624.png\" alt=\"\" class=\"wp-image-7364\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06-1024x624.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06-300x183.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06-768x468.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-06.png 1276w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig.6 &#8211; <em>Choix du groupe d&rsquo;\u00e9quipements concern\u00e9.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Dans la liste des images de containers, la nouvelle appara\u00eet juste apr\u00e8s la liste des containers de d\u00e9monstration. Comme nous l&rsquo;avions vu dans l&rsquo;article pr\u00e9c\u00e9dent, il est possible de <strong>masquer les containers de d\u00e9monstration<\/strong> pour un groupe d&rsquo;\u00e9quipements.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-07.png\"><img loading=\"lazy\" decoding=\"async\" width=\"677\" height=\"463\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-07.png\" alt=\"\" class=\"wp-image-7366\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-07.png 677w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-07-300x205.png 300w\" sizes=\"auto, (max-width: 677px) 100vw, 677px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 7 &#8211; <em>S\u00e9lection du container \u00e0 distribuer.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Au bout de quelques instants, le container est indiqu\u00e9 comme \u00ab\u00a0<em>Running<\/em>\u00a0\u00bb  en bas \u00e0 droite de la fen\u00eatre.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"631\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08-1024x631.png\" alt=\"\" class=\"wp-image-7368\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08-1024x631.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08-300x185.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08-768x474.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-08.png 1273w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 8 &#8211; <em>Container en cours d&rsquo;ex\u00e9cution.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<h3 id=\"T1.1.4\" class=\"wp-block-heading\">1.1.4 &#8211; V\u00e9rification du fonctionnement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On peut tester la connexion en appelant un client <code>ssh<\/code> en lui indiquant le num\u00e9ro de port <code>12345<\/code> pr\u00e9c\u00e9d\u00e9 de l&rsquo;option <code>-p<\/code> et l&rsquo;adresse IP indiqu\u00e9e dans le quart inf\u00e9rieur droit de l&rsquo;\u00e9cran&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ssh -p 12345 eris@192.168.3.36<\/strong>\nThe authenticity of host '&#91;192.168.3.36]:12345 (&#91;192.168.3.36]:12345)' can't be established.\nED25519 key fingerprint is: SHA256:vQAhsmy6cRlfUkjCq4t49njKXpzyw97qeuzk7CLC7YI\nThis key is not known by any other names.\nAre you sure you want to continue connecting (yes\/no\/&#91;fingerprint])? yes\nWarning: Permanently added '&#91;192.168.3.36]:12345' (ED25519) to the list of known hosts.\neris@192.168.3.36's password: <strong>linux<\/strong>\nWelcome to Alpine!\n\nThe Alpine Wiki contains a large amount of how-to guides and general\ninformation about administrating Alpine systems.\nSee &lt;https:\/\/wiki.alpinelinux.org\/&gt;.\n\nYou can setup the system with the command: setup-alpine\n\nYou may change this message by editing \/etc\/motd.\n\n2cd5e9876032:~$  <strong>ps aux<\/strong>\nPID   USER     TIME  COMMAND\n    1 root      0:00 \/sbin\/docker-init -- \/usr\/sbin\/sshd -D\n    7 root      0:00 sshd: \/usr\/sbin\/sshd -D &#91;listener] 0 of 10-100 startups\n    8 root      0:00 sshd-session: eris &#91;priv]\n   10 eris      0:00 sshd-session: eris@pts\/1\n   11 eris      0:00 -sh\n   12 eris      0:00 ps aux\n\n2cd5e9876032:~$<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nous pouvons alors passer des commandes comme nous l&rsquo;avons vu dans l&rsquo;article pr\u00e9c\u00e9dent.<\/p>\n\n\n\n<h2 id=\"T1.2\" class=\"wp-block-heading\">1.2 &#8211; Serveur Web avec configuration et pages HTML personnalis\u00e9es<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour notre second exemple de service Linux standard, nous allons nous int\u00e9resser au r\u00e9pertoire <code>02-api-test-web<\/code>. Il contient un fichier <code>Dockerfile<\/code> pr\u00e9sent\u00e9 en version un peu simplifi\u00e9e ci-dessous et deux sous-r\u00e9pertoires&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><strong>deploy\/<\/strong><\/code> contenant le fichier de configuration et le script de lancement du serveur Nginx,<\/li>\n\n\n\n<li><code><strong>frontend\/<\/strong><\/code> contenant des pages HTML, des fichiers graphiques et une feuille de style CSS.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">En faisant appel \u00e0 l&rsquo;API Rest d&rsquo;Eris Linux, nous commen\u00e7ons \u00e0 voir le d\u00e9veloppement de code m\u00e9tier personnalis\u00e9. Le <code>Dockerfile<\/code> contient&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\n\nRUN apk add --no-cache  nginx busybox-suid\n\nCOPY frontend\/ \/usr\/share\/nginx\/html\n\nCOPY deploy\/nginx.conf \/etc\/nginx\/http.d\/default.conf\n\nCOPY deploy\/start.sh \/start.sh\nRUN chmod +x \/start.sh\n\nEXPOSE 80\n\nCMD &#91;\"\/start.sh\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour cr\u00e9er le fichier image du container on emploie&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>.\/create-container  arm64  .\/02-api-test-web  api-test-web<\/strong>\n#0 building with \"default\" instance using docker driver\n &#91;...]\n#11 exporting to client tarball\n#11 sending tarball 0.0s done\n#11 DONE 0.0s\n\/home\/cpb\/Eris-Linux-Containers\n\n$ <strong>ls\n<\/strong> &#91;...]    api-test-web.tar.bz2  &#91;...]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le m\u00e9canisme de chargement de l&rsquo;image sur le serveur Eris Linux est identique \u00e0 celui observ\u00e9 pr\u00e9c\u00e9demment. On obtient une nouvelle image disponible.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"486\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2-1024x486.png\" alt=\"\" class=\"wp-image-7390\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2-1024x486.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2-300x142.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2-768x364.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-09-2.png 1279w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 9 &#8211; <em>Configuration du container avec IHM web.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Une fois l&rsquo;image d\u00e9ploy\u00e9e sur un groupe d&rsquo;\u00e9quipements, on peut se <strong>connecter sur le serveur Web<\/strong> avec un navigateur en utilisant l&rsquo;URL&nbsp;: <code>http:\/\/&lt;ip-carte&gt;:8888<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ce container propose une IHM pour communiquer avec l&rsquo;API Rest d&rsquo;Eris Linux qui donne acc\u00e8s \u00e0 diverses fonctionnalit\u00e9s syst\u00e8me.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"542\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10-1024x542.png\" alt=\"\" class=\"wp-image-7393\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10-1024x542.png 1024w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10-300x159.png 300w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10-768x407.png 768w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-10.png 1135w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 10 &#8211; <em>IHM Web d&rsquo;acc\u00e8s \u00e0 l&rsquo;API Eris Linux.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Dans cet exemple comme dans le pr\u00e9c\u00e9dent, il n&rsquo;y a pas d&rsquo;acc\u00e8s direct au mat\u00e9riel de la carte embarqu\u00e9e, on passe par l&rsquo;API Eris Linux. Dans l&rsquo;exemple suivant nous allons acc\u00e9der au mat\u00e9riel via l&rsquo;arborescence <code>\/sys\/<\/code> de Linux.<\/p>\n\n\n\n<h1 id=\"T2\" class=\"wp-block-heading\">2 &#8211; Scripts shell acc\u00e9dant au mat\u00e9riel<\/h1>\n\n\n\n<h2 id=\"T2.1\" class=\"wp-block-heading\">2.1 &#8211; Acc\u00e8s aux GPIO<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Dans un container, le code est isol\u00e9 du syst\u00e8me. Il ne voit que les processus s&rsquo;ex\u00e9cutant dans le m\u00eame container. L&rsquo;arborescence de fichiers dans lequel il \u00e9volue est \u00e9galement sp\u00e9cifique \u00e0 ce container. Mais il est n\u00e9anmoins possible de r\u00e9clamer un acc\u00e8s aux p\u00e9riph\u00e9riques en utilisant un container <strong>privil\u00e9gi\u00e9<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sous Linux, lorsqu&rsquo;un processus veut communiquer avec un p\u00e9riph\u00e9rique mat\u00e9riel, il doit &#8211; sauf dans de tr\u00e8s rares cas &#8211; passer par l&rsquo;interm\u00e9diaire d&rsquo;un <em><strong>driver<\/strong><\/em> (code d\u00e9di\u00e9 au p\u00e9riph\u00e9rique s&rsquo;ex\u00e9cutant dans le <em>kernel<\/em> Linux). Pour cela, l&rsquo;approche la plus courante est d&rsquo;invoquer un \u00ab\u00a0<strong>appel syst\u00e8me<\/strong>\u00a0\u00bb (<em>syscall<\/em>) comme <code>read()<\/code>, <code>write()<\/code>, <code>ioctl()<\/code>, <code>poll()<\/code>, <code>mmap()<\/code> etc. sur un fichier sp\u00e9cial se trouvant dans le r\u00e9pertoire <code><strong>\/dev\/<\/strong><\/code> du syst\u00e8me. Une autre possibilit\u00e9 est de faire des requ\u00eates de plus haut niveau au <em>driver<\/em> en envoyant des consignes par l&rsquo;interm\u00e9diaire de l&rsquo;arborescence de fichiers <code><strong>\/sys\/<\/strong><\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La plupart des syst\u00e8mes embarqu\u00e9s offrent des possibilit\u00e9s d&rsquo;entr\u00e9es\/sorties \u00e9lectroniques en utilisant les broches GPIO (<em>General Purpose Input Output<\/em>) du processeur. Ces lignes \u00e9lectriques peuvent \u00eatre configur\u00e9es \u00e0 la demande en entr\u00e9e ou en sortie, puis leur \u00e9tat peut \u00eatre consult\u00e9 ou modifi\u00e9 depuis une application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous n&rsquo;avons pas de container de d\u00e9monstration utilisant directement les GPIO car il s&rsquo;agit d&rsquo;op\u00e9rations assez sensibles, et une erreur de manipulation peut vite d\u00e9truire une carte \u00e9lectronique. Par exemple configurer en sortie une broche normalement pr\u00e9vue pour \u00eatre une entr\u00e9e et lui forcer un \u00e9tat \u00e9lectrique diff\u00e9rent de celui impos\u00e9 par le circuit externe d\u00e9clenche un court-circuit sur le processeur, ce qui peut endommager la sortie GPIO, voire le processeur, avec \u00e0 la cl\u00e9 une petite fum\u00e9e bleut\u00e9e et la commande d&rsquo;une nouvelle carte \u00e9lectronique.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si vous souhaitez exp\u00e9rimenter les entr\u00e9es-sorties de votre carte \u00e9lectronique, vous pouvez facilement ajouter la biblioth\u00e8que <code>libgpiod<\/code> (qui est automatiquement accompagn\u00e9e de ses utilitaires) dans le <code>Dockerfile<\/code> que nous avons utilis\u00e9 pour le serveur SSH. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\n\nRUN apk update                                                   \\\n &amp;&amp; apk add --no-cache <strong>libgpiod<\/strong> openssh                          \\\n &amp;&amp; ssh-keygen -A                                                \\\n &#91;...]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La biblioth\u00e8que <code>libgpiod<\/code> communique avec les contr\u00f4leurs d&rsquo;entr\u00e9e-sortie par l&rsquo;interm\u00e9diaire des fichiers sp\u00e9ciaux <code>\/dev\/gpiochip##<\/code>. Pour que cela soit possible depuis l&rsquo;int\u00e9rieur d&rsquo;un container il est indispensable que celui ci ait \u00e9t\u00e9 d\u00e9clar\u00e9 comme <strong>privil\u00e9gi\u00e9<\/strong> :<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-11.png\"><img loading=\"lazy\" decoding=\"async\" width=\"673\" height=\"591\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-11.png\" alt=\"\" class=\"wp-image-7416\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-11.png 673w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-11-300x263.png 300w\" sizes=\"auto, (max-width: 673px) 100vw, 673px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig.11 &#8211; <em>Container privil\u00e9gi\u00e9.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Une fois l&rsquo;image produite, install\u00e9e sur le serveur Eris Linux et le container d\u00e9ploy\u00e9 sur votre carte, vous pourrez vous connecter en utilisant SSH puis appeler les commandes <code>gpiodetect<\/code>, <code>gpioinfo<\/code>, <code>gpioget<\/code>, <code>gpioset<\/code> et <code>gpiomon<\/code>. Le comportement est parfois un peu inattendu (surtout celui de <code>gpioset<\/code>), on trouvera une explication d\u00e9taill\u00e9e dans <a href=\"https:\/\/www.blaess.fr\/christophe\/2018\/10\/15\/pilotage-de-gpio-avec-lapi-libgpiod-partie-1\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>cet article<\/strong><\/a>.<\/p>\n\n\n\n<h2 id=\"T2.2\" class=\"wp-block-heading\">2.2 &#8211; Configuration des <em>triggers<\/em> de LEDs<\/h2>\n\n\n\n<h3 id=\"T2.2.1\" class=\"wp-block-heading\">2.2.1 &#8211; Principe<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Les LEDs connues du noyau Linux peuvent \u00eatre pilot\u00e9es en configurant un <em><strong>trigger<\/strong><\/em> (actionneur) qui allumera ou \u00e9teindra la LED en fonction de circonstances externes. Il ne s&rsquo;agit plus d&rsquo;allumer ou d&rsquo;\u00e9teindre explicitement une sortie GPIO, mais de demander \u00e0 un composant logiciel du <em>kernel<\/em> de prendre en charge la broche de sortie \u00e9lectrique et de la piloter suivant les circonstances en cours.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Par exemple, il existe des <em>triggers<\/em> pour allumer ou \u00e9teindre des LEDs en fonction de l&rsquo;activit\u00e9 d&rsquo;un disque dur ou d&rsquo;un SSD, ou encore en fonction du trafic r\u00e9seau Ethernet. Des <em>triggers<\/em> peuvent piloter des LEDs en fonction de l&rsquo;\u00e9tat des touches CAPS Lock, NUM Lock, SCROLL Lock, etc. Des <em>triggers<\/em> peuvent refl\u00e9ter sur des LEDs la charge d&rsquo;un ou plusieurs c\u0153urs de CPU ou le d\u00e9clenchement d&rsquo;un <em>kernel panic<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les triggers qui vont nous int\u00e9resser ici sont plus simples&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u00ab\u00a0<code>default-on<\/code>\u00a0\u00bb allume la LED concern\u00e9e de mani\u00e8re persistante<\/li>\n\n\n\n<li>\u00ab\u00a0<code>timer<\/code>\u00a0\u00bb la fait clignoter \u00e0 une fr\u00e9quence d&rsquo;un Hz.<\/li>\n\n\n\n<li>\u00ab\u00a0<code>heartbeat<\/code>\u00a0\u00bb simule un battement de c\u0153ur repr\u00e9sentatif de la charge du processeur.<\/li>\n\n\n\n<li>\u00ab\u00a0<code>none<\/code>\u00a0\u00bb \u00e9teint la LED indiqu\u00e9e.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;avantage de ces <em>triggers<\/em> de haut-niveau est qu&rsquo;ils sont ind\u00e9pendants du syst\u00e8me sous-jacent, le script shell que nous allons voir peut fonctionner sur toutes les machines Linux abritant une ou plusieurs LEDs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La configuration de ces <em>triggers<\/em> se fait en lisant et \u00e9crivant dans le pseudo-fichier <code>\/sys\/class\/leds\/&lt;led-name&gt;\/trigger<\/code> : la lecture de ce fichier affiche la liste de tous les <em><span style=\"text-decoration: underline;\">triggers<\/span><\/em> connus, celui s\u00e9lectionn\u00e9 \u00e9tant encadr\u00e9. L&rsquo;\u00e9criture d&rsquo;un nom de <em>trigger<\/em> dans ce fichier permet de le s\u00e9lectionner.<\/p>\n\n\n\n<h3 id=\"T2.2.2\" class=\"wp-block-heading\">2.2.2 &#8211; Script et Dockerfile<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le script shell que nous allons installer est tr\u00e8s simple&nbsp;: il s\u00e9lectionne les triggers de toutes les LEDs du syst\u00e8me toutes les 15 secondes en alternant entre les quatre indiqu\u00e9s ci-dessus.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#! \/bin\/sh\nwhile true\ndo\n\tfor trigger in timer default-on heartbeat none\n\tdo\n\n\t\tfor led in \/sys\/class\/leds\/*\n\t\tdo\n\t\t\tif &#91; -f \"${led}\/trigger\" ]\n\t\t\tthen\n\t\t\t\techo \"${trigger}\" > \"${led}\/trigger\"\n\t\t\tfi\n\t\tdone\n\t\tsleep 15\n\tdone\ndone\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le fichier <code>Dockerfile<\/code> doit copier ce script sur le syst\u00e8me cible et lui donner les droits d&rsquo;ex\u00e9cution ad\u00e9quats.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\n\nCOPY  led-trigger-setup.sh  \/led-trigger-setup.sh\nRUN  chmod  +x  \/led-trigger-setup.sh\n\nCMD &#91;\"\/led-trigger-setup.sh\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Comme pour l&rsquo;exemple pr\u00e9c\u00e9dent, l&rsquo;image de container doit \u00eatre dot\u00e9e de l&rsquo;attribut \u00ab\u00a0<em><strong>privileged<\/strong><\/em>\u00a0\u00bb qui lui permettra de disposer d&rsquo;une arborescence <code>\/sys<\/code> compl\u00e8te (au m\u00eame titre que le r\u00e9pertoire <code>\/dev<\/code> de l&rsquo;exemple pr\u00e9c\u00e9dent).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Une fois l&rsquo;image construite, install\u00e9e sur le serveur Eris Linux et d\u00e9ploy\u00e9e vers une carte cible, on pourra observer que toutes les LEDs changent de comportement toutes les 15 secondes&nbsp;: clignotement -&gt; allumage -&gt; battement de c\u0153ur -&gt; extinction.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les principes que nous avons vus pour l&rsquo;installation des scripts shell sont \u00e9videmment valables pour tout langage interpr\u00e9t\u00e9 (Python, LUA, NodeJS, etc.) \u00e0 condition d&rsquo;installer le package de l&rsquo;interpr\u00e9teur dans l&rsquo;image en ajoutant une ligne comme<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>RUN apk add --no-cache python3<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">dans le <code>Dockerfile<\/code> \u00e0 la suite de la ligne <code>FROM Alpine...<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous reparlerons de Python un peu plus loin, mais nous allons \u00e0 pr\u00e9sent nous concentrer sur un deuxi\u00e8me type d&rsquo;applications&nbsp;: celles dont le code <em>source<\/em> original est <strong>compil\u00e9<\/strong> pour produire un fichier ex\u00e9cutable directement exploitable par le processeur de la carte cible.<\/p>\n\n\n\n<h1 id=\"T3\" class=\"wp-block-heading\">3 &#8211; Cross-compilation<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">La plupart des d\u00e9veloppeurs travaillent sur une machine (PC ou serveur distant) fonctionnant sur un processeur de la famille x86-64. Les syst\u00e8mes embarqu\u00e9s les plus courants utilisent des processeurs de la famille ARM64 et parfois RISC-V. Pour produire du code ex\u00e9cutable fonctionnant sur la cible embarqu\u00e9e, il faudra donc r\u00e9aliser une \u00e9tape de <strong>cross-compilation<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour la plupart des syst\u00e8mes embarqu\u00e9s, on passe par une phase un peu fastidieuse de t\u00e9l\u00e9chargement et d&rsquo;installation d&rsquo;un <strong>cross-compiler<\/strong> et d&rsquo;un <strong>SDK<\/strong> (<em>Software Development Kit<\/em>). Bonne nouvelle&nbsp;: <strong>Avec Eris Linux vous n&rsquo;avez pas besoin d&rsquo;installer de cha\u00eene de cross-compilation ni de SDK<\/strong>, tout va \u00eatre pris en charge directement par le mode Buildx de Docker&nbsp;!<\/p>\n\n\n\n<h2 id=\"T3.1\" class=\"wp-block-heading\">3.1 &#8211; Docker Buildx<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker <strong>Buildx<\/strong> s&rsquo;appuie sur <strong>QEMU<\/strong> et le m\u00e9canisme Linux <code><strong>binfmt_misc<\/strong><\/code>. Ce dernier permet d\u2019associer certains formats d\u2019ex\u00e9cutables \u00e0 un interpr\u00e9teur&nbsp;: lorsque sur une machine x86-64 on demande au noyau Linux de lancer un ex\u00e9cutable au format ARM64, il fait appel l\u2019\u00e9mulateur <code>qemu-aarch64<\/code> appropri\u00e9. Cette \u00e9mulation est transparente pour l&rsquo;utilisateur. Elle est aussi transparente lorsqu&rsquo;il s&rsquo;agit de lancer des commandes depuis un <code>Dockerfile<\/code>. Il est par exemple possible d\u2019utiliser dans le container un compilateur GCC ARM64 natif, lui-m\u00eame ex\u00e9cut\u00e9 sous QEMU, pour compiler notre application. Il ne s\u2019agit donc pas v\u00e9ritablement de cross-compilation, Buildx recr\u00e9e plut\u00f4t, par \u00e9mulation, un environnement ARM64 sur la machine x86-64. Cette m\u00e9thode, l\u00e9g\u00e8rement plus lente <span style=\"text-decoration: underline;\">q<\/span>u&rsquo;une cross-compilation traditionnelle, est extr\u00eamement pratique.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour faire appel \u00e0 Buildx de mani\u00e8re simplifi\u00e9e, un script est fourni dans notre <em>repository<\/em> d&rsquo;exemples&nbsp;: <code><strong>create-container<\/strong><\/code>. La ligne de commande exacte \u00e0 utiliser est pr\u00e9cis\u00e9e dans le fichier <code>README.md<\/code> de chaque sous-r\u00e9pertoire d&rsquo;exemple.<\/p>\n\n\n\n<h2 id=\"T3.2\" class=\"wp-block-heading\">3.2 &#8211; Pr\u00e9requis<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour le bon fonctionnement de l&rsquo;environnement de production, plusieurs packages doivent \u00eatre install\u00e9s sur la machine de d\u00e9veloppement, en utilisant la commande <code>apt<\/code>, <code>yum<\/code>, <code>dnf<\/code>, etc. correspondant \u00e0 votre distribution.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>docker-ce<\/code><\/li>\n\n\n\n<li><code>docker-ce-cli<\/code><\/li>\n\n\n\n<li><code>docker-buildx-plugin<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Les noms des packages d\u00e9pendent de la distribution Linux utilis\u00e9e.<\/p>\n\n\n\n<h2 id=\"T3.3\" class=\"wp-block-heading\">3.3 &#8211; Programme compil\u00e9 en C<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Traditionnellement, l&rsquo;exploration d&rsquo;un environnement de d\u00e9veloppement commence par l&rsquo;\u00e9criture d&rsquo;un programme affichant \u00ab\u00a0<em><strong>Hello World!<\/strong><\/em>\u00a0\u00bb sur la console. Ceci permet de valider la bonne configuration de la cha\u00eene d&rsquo;outils de compilation, d&rsquo;autant plus dans un environnement embarqu\u00e9 o\u00f9 le processeur de compilation et celui d&rsquo;ex\u00e9cution sont d&rsquo;architectures diff\u00e9rentes. Ne d\u00e9rogeons pas \u00e0 cette habitude, et pr\u00e9parons un container faisant fonctionner une application affichant un message, compil\u00e9e depuis un fichier source en <strong>langage C<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le programme propos\u00e9 dans le sous-r\u00e9pertoire <code>05-hello-world-in-c<\/code> est tr\u00e8s simple&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;unistd.h&gt;\n\nint main(void)\n{\n\tfor (;;) {\n\t\tprintf(\"Hello from Eris Linux container! (C language program)\\n\");\n\t\tsleep(5);\n\t}\n\treturn 0;\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il est accompagn\u00e9 d&rsquo;un fichier <code><strong>Makefile<\/strong><\/code> pour le compiler. Vous pouvez lui pr\u00e9f\u00e9rer un autre outil de production plus moderne comme <code>Cmake<\/code> ou <code>meson.build<\/code>. Ce <code>Makefile<\/code> est tr\u00e8s g\u00e9n\u00e9rique, il suffit essentiellement de modifier les quatre premi\u00e8res lignes pour pouvoir le r\u00e9utiliser pour d&rsquo;autres projets&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>APP = hello-world-in-c\nOBJS = hello-world-in-c.o\nCFLAGS += -Wall -g\nLDFLAGS =\n\n\nall: $(APP)\n\n$(APP): $(OBJS)\n\t$(CC) $(CFLAGS) $(LDFLAGS)  $^ -o $@ $(LDLIBS)\n\n%.o: %.c $(INC)\n\t$(CC) -c $(CFLAGS) $&lt;\n\nclean:\n\trm -rf out *.o $(APP)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le point le plus important est un <code><strong>Dockerfile<\/strong><\/code> qui va assurer la compilation pour la cible d\u00e9sir\u00e9e. Ce <code>Dockerfile<\/code> est compos\u00e9 en <strong>deux parties<\/strong> successives. La premi\u00e8re partie fait juste la compilation du fichier source ci-dessus en s&rsquo;ex\u00e9cutant dans un container d\u00e9di\u00e9 (sur la machine de production) qui est port\u00e9 par l&rsquo;\u00e9mulateur Qemu.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM  alpine:latest AS build\nRUN apk add --no-cache build-base file\n\nWORKDIR \/src\nCOPY hello-world-in-c.c Makefile .\n\nRUN make\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La seconde partie du <code>Dockerfile<\/code> cr\u00e9e l&rsquo;image du container pour la cible en embarquant le code compil\u00e9 pr\u00e9c\u00e9dent&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\nCOPY --from=build \/src\/hello-world-in-c \/hello-world-in-c\nCMD &#91;\"\/hello-world-in-c\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">C&rsquo;est le script <code><strong>create-container<\/strong><\/code> que nous avons vu plus haut qui appellera <code>docker buildx<\/code> en lui indiquant le type de cible d\u00e9sir\u00e9 (argument <code>--platform<\/code>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lorsqu&rsquo;on appelle&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>$ <strong>.\/create-container  arm64  05-hello-world-in-c\/  hello-world-in-c<\/strong><\/code><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le script utilise <code>docker buildx<\/code> pour nous pr\u00e9parer un fichier <code>hello-world-in-c.tar.bz2<\/code> repr\u00e9sentant le container \u00e0 d\u00e9ployer. Une fois cette image de container envoy\u00e9e sur le serveur Eris Linux, et d\u00e9ploy\u00e9e sur une cible, on peut voir le message \u00ab\u00a0<code>Hello from Eris Linux container! (C language program)<\/code>\u00a0\u00bb s&rsquo;afficher toutes les cinq secondes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous pouvons \u00e9galement observer l&rsquo;installation des fichiers pour Qemu utilis\u00e9s par <code>binfmt_misc<\/code> durant la premi\u00e8re ex\u00e9cution du script. Avant l&rsquo;ex\u00e9cution du script&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls  \/proc\/sys\/fs\/binfmt_misc\/<\/strong>\npython3.14  register  status\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Apr\u00e8s ex\u00e9cution du script&nbsp;: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls  \/proc\/sys\/fs\/binfmt_misc\/<\/strong>\npython3.14  <strong>qemu-aarch64<\/strong>  register  status\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;\u00e9mulateur <code>qemu-aarch64<\/code> permet de faire fonctionner un ex\u00e9cutable pr\u00e9vu pour ARM64 sur une architecture diff\u00e9rente.<\/p>\n\n\n\n<h2 id=\"T3.4\" class=\"wp-block-heading\">3.4 &#8211; Programme C utilisant le r\u00e9seau<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour voir les messages de l&rsquo;exemple ci-dessus il est n\u00e9cessaire de disposer d&rsquo;un \u00e9cran sur la cible qui ne soit pas occup\u00e9 par un affichage graphique. Si notre cible n&rsquo;a pas d&rsquo;afficheur on peut pr\u00e9f\u00e9rer un programme qui envoie son message sur une connexion r\u00e9seau TCP\/IP. Il suffit de modifier le fichier source ainsi&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;time.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;arpa\/inet.h&gt;\n#include &lt;netinet\/in.h&gt;\n#include &lt;sys\/socket.h&gt;\n\n\n#define PORT_NUMBER   3500\n\n\nint main(void)\n{\n\tint sock;\n\tsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);\n\tif (sock &lt; 0) {\n\t\tperror(\"socket\");\n\t\texit(EXIT_FAILURE);\n\t}\n\tint reuse = 1;\n\tif (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &amp;reuse, sizeof(reuse)) &lt; 0) {\n\t\tperror(\"setsockopt\");\n\t\texit(EXIT_FAILURE);\n\t}\n\tstruct sockaddr_in addr;\n\tmemset(&amp;addr, 0, sizeof(addr));\n\taddr.sin_family = AF_INET;\n\taddr.sin_port = htons(PORT_NUMBER);\n\taddr.sin_addr.s_addr = htonl(INADDR_ANY);\n\tif (bind(sock, (struct sockaddr *) &amp;addr, sizeof(addr)) &lt; 0) {\n\t\tperror(\"bind\");\n\t\texit(EXIT_FAILURE);\n\t}\n\tif (listen(sock, 128) &lt; 0) {\n\t\tperror(\"listen\");\n\t\texit(EXIT_FAILURE);\n\t}\n\n\tfor (;;) {\n\t\tconst char message&#91;] = \"Hello from Eris Linux container! (C language program)\\n\";\n\t\tint new_sock;\n\t\tif ((new_sock = accept(sock, NULL, NULL)) &lt; 0)\n\t\t\tcontinue;\n\t\t(void) send(new_sock, message, strlen(message), MSG_NOSIGNAL);\n\t\tshutdown(new_sock, SHUT_RDWR);\n\t\tclose(new_sock);\n\t}\n}\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Bien s\u00fbr lors du d\u00e9ploiement du container il sera n\u00e9cessaire d&rsquo;<strong>exporter le port TCP<\/strong> pour qu&rsquo;il soit accessible depuis l&rsquo;ext\u00e9rieur du container.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-12-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"673\" height=\"591\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-12-1.png\" alt=\"\" class=\"wp-image-7457\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-12-1.png 673w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-12-1-300x263.png 300w\" sizes=\"auto, (max-width: 673px) 100vw, 673px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 12 &#8211; <em>Remote \u00ab\u00a0Hello World\u00a0\u00bb avec port TCP 3500 export\u00e9.<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">On pourra se connecter sur le serveur TCP avec n&rsquo;importe quel client g\u00e9n\u00e9rique comme <code>telnet<\/code> ou <code>nc<\/code> par exemple&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>nc  192.168.3.36  3500<\/strong>\nHello from Eris Linux container! (C language program)\n<\/code><\/pre>\n\n\n\n<h2 id=\"T3.5\" class=\"wp-block-heading\">3.5 &#8211; Programme C appelant l&rsquo;API d&rsquo;Eris Linux<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nous avons vu comment un programme fonctionnant dans un container peut acc\u00e9der au mat\u00e9riel en communiquant avec les fichiers sp\u00e9ciaux de <code>\/dev<\/code> ou avec les points d&rsquo;entr\u00e9e virtuels de l&rsquo;arborescence <code>\/sys<\/code>. Ceci \u00e0 condition que le container soit <strong>privil\u00e9gi\u00e9<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cette situation doit toutefois \u00eatre \u00e9vit\u00e9e autant que possible car l&rsquo;utilisation d&rsquo;un container privil\u00e9gi\u00e9 d\u00e9grade la s\u00e9curit\u00e9 du syst\u00e8me, bas\u00e9e sur l&rsquo;isolation entre le syst\u00e8me Eris Linux de base et les containers applicatifs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il est nettement pr\u00e9f\u00e9rable, pour communiquer avec le syst\u00e8me Eris Linux et le mat\u00e9riel sous-jacent de faire appel \u00e0 l&rsquo;<strong>API Eris Linux<\/strong>. Cette interface de programmation sera d\u00e9taill\u00e9e dans le prochain article. Elle est relativement compl\u00e8te mais nous continuons \u00e0 l&rsquo;enrichir r\u00e9guli\u00e8rement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;API Eris Linux est propos\u00e9e sous deux formes aux possibilit\u00e9s identiques&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>une <strong>API Rest<\/strong> propos\u00e9e par un serveur <strong>HTTP<\/strong> inclus dans le syst\u00e8me Eris Linux, nous en avons eu un aper\u00e7u dans le <a href=\"#T1.2\">paragraphe 1.2<\/a>.<\/li>\n\n\n\n<li>une <strong>biblioth\u00e8que<\/strong> pour les applications C\/C++ disponible dans les containers sous le nom <code><strong>liberis<\/strong><\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">La mise \u00e0 disposition de la biblioth\u00e8que pour les applications \u00e9crites en Python ou en Rust fait partie des prochaines \u00e9volutions d&rsquo;Eris Linux.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous allons examiner le contenu du r\u00e9pertoire <code>06-api-test-tcp\/<\/code> qui impl\u00e9mente un service TCP, \u00e0 la mani\u00e8re du pr\u00e9c\u00e9dent exemple, qui offre une petite interface pour interroger l&rsquo;API Eris Linux, repr\u00e9sent\u00e9e par la biblioth\u00e8que <code>liberis<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les sources du programme se trouvent dans un sous-r\u00e9pertoire d\u00e9di\u00e9 et reposent sur huit fichiers source et autant de fichiers <em>headers<\/em> (d&rsquo;extension <code>.h<\/code>)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls 06-api-test-tcp\/sources\/<\/strong>\neris-api-test.c  include   network-api-test.c  system-api-test.c  update-api-test.c\ngpio-api-test.c  Makefile  sbom-api-test.c     time-api-test.c    wdog-api-test.c\n\n$ <strong>ls 06-api-test-tcp\/sources\/include\/<\/strong>\neris-api-test.h  network-api-test.h  system-api-test.h  update-api-test.h\ngpio-api-test.h  sbom-api-test.h     time-api-test.h    wdog-api-test.h\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le fichier <code>Makefile<\/code> pr\u00e9sent avec les sources est un peu plus long que les pr\u00e9c\u00e9dents car il liste les fichiers objets \u00e0 produire et les fichiers headers \u00e0 inclure&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>APP = eris-api-test\n\nOBJS =               \\\n  eris-api-test.o    \\\n  gpio-api-test.o    \\\n  network-api-test.o \\\n  sbom-api-test.o    \\\n  system-api-test.o  \\\n  time-api-test.o    \\\n  update-api-test.o  \\\n  wdog-api-test.o    \\\n\nINC = liberis.h      \\\n  eris-api-test.h    \\\n  gpio-api-test.h    \\\n  network-api-test.h \\\n  sbom-api-test.h    \\\n  system-api-test.h  \\\n  time-api-test.h    \\\n  update-api-test.h  \\\n  wdog-api-test.h    \\\n\nCFLAGS  += -Wall -g -I include\/\nLDLIBS  += -leris -lcurl\n\nall: $(APP)\n\n$(APP): $(OBJS)\n\t$(CC) $(CFLAGS) $(LDFLAGS)  $^ -o $@ $(LDLIBS)\n\n%.o: %.c $(INC)\n\t$(CC) -c $(CFLAGS) $&lt;\n\nclean:\n\trm -rf out *.o $(APP)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On notera la pr\u00e9sence dans la variable <code>LDLIBS<\/code> de l&rsquo;argument <code><strong>-leris<\/strong><\/code> qui indique au <em>linker<\/em> que l&rsquo;application fait appel \u00e0 la biblioth\u00e8que <code>liberis<\/code> et de <code><strong>-lcurl<\/strong><\/code> car <code>liberis<\/code> fait \u00e0 son tour appel \u00e0 la biblioth\u00e8que <code>libcurl<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le <code>Dockerfile<\/code> est un peu plus complexe que le pr\u00e9c\u00e9dent. Dans sa premi\u00e8re moiti\u00e9, il commence par t\u00e9l\u00e9charger puis compiler la biblioth\u00e8que <code>liberis<\/code>, puis il compile l&rsquo;application qui en d\u00e9pend. La biblioth\u00e8que <code>liberis<\/code> est compil\u00e9e sous forme statique. Ainsi ses fonctions n\u00e9cessaires pour l&rsquo;application seront directement copi\u00e9es dans l&rsquo;ex\u00e9cutable. Ceci \u00e9vite de devoir g\u00e9rer l&rsquo;installation de la biblioth\u00e8que dynamique dans le container. La biblioth\u00e8que <code>liberis<\/code> d\u00e9pendant de la <code>libcurl<\/code>, on remarquera la pr\u00e9sence de <code>curl-dev<\/code> dans la liste des packages install\u00e9s avec <code>apk add<\/code> \u00e0 la deuxi\u00e8me ligne du fichier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest AS build\nRUN apk add --no-cache build-base file curl-dev git ca-certificates\n\nWORKDIR \/src\nARG LIBERIS_REPO=https:\/\/github.com\/Eris-Linux\/liberis.git\nARG LIBERIS_BRANCH=master\nRUN git clone --depth 1 --branch \"${LIBERIS_BRANCH}\" \"${LIBERIS_REPO}\" \/src\/liberis\n\nWORKDIR \/src\/liberis\nRUN make static\nRUN make static_install\nWORKDIR \/src\n\nCOPY sources\/* .\nRUN make\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La seconde moiti\u00e9 du <code>Dockerfile<\/code> installe l&rsquo;ex\u00e9cutable obtenu dans un container d\u00e9di\u00e9. La <code>libcurl<\/code> n\u00e9cessaire pour <code>liberis<\/code> est ajout\u00e9e depuis les packages standards de la distribution Alpine.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\nRUN apk add --no-cache libcurl\nCOPY --from=build \/src\/eris-api-test \/eris-api-test\nCMD &#91;\"\/eris-api-test\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Une fois le code compil\u00e9, l&rsquo;image produite et install\u00e9e sur le serveur Eris Linux (en exportant le port TCP 10000), puis d\u00e9ploy\u00e9e sur une carte cible, on peut s&rsquo;y connecter depuis une autre machine&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>nc  192.168.3.36  10000<\/strong>\n\n**** Eris Linux API test *****\n\n1: System Identification        6: Software Bill of Materials \n2: System &amp; Containers Update   7: Network Interfaces         \n3: Time Setup                   8: General Purpose I\/O        \n4: Watchdog Configuration       9: Display Features(*)        \n5: Audio Features(*)                                          \n0: Quit                                                       \n                      (*) Coming soon                         \nYour choice: <strong>7<\/strong>\n\n**** Eris Linux Network Interfaces Setup *****\n\n1:  Get list of interfaces    7: Get nameserver address      \n2:  Get interface status      8: Set nameserver address      \n3:  Set interface status      9: Scan Wifi Access Points     \n4:  Get interface config     10: Connect to Wifi AP (BROKEN?)\n5:  Set interface config     11: Disconnect from Wifi A.P.   \n6:  Is interface wireless    12: Get Wifi quality            \n0:  Return                                                   \n\nYour choice: <strong>1\n<\/strong>List of network interfaces : wlan0 eth0\n\nYour choice: <strong>0<\/strong>\n\n**** Eris Linux API test *****\n\n1: System Identification        6: Software Bill of Materials \n2: System &amp; Containers Update   7: Network Interfaces         \n3: Time Setup                   8: General Purpose I\/O        \n4: Watchdog Configuration       9: Display Features(*)        \n5: Audio Features(*)                                          \n0: Quit                                                       \n                      (*) Coming soon                         \nYour choice: <strong>0<\/strong>\n\n$<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cette communication en mode texte est pratique lorsqu&rsquo;on doit se connecter \u00e0 distance sur une carte, mais il faut reconna\u00eetre que c&rsquo;est un peu le \u00ab\u00a0degr\u00e9 0\u00a0\u00bb de l&rsquo;interface utilisateur. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans de nombreux cas, notamment lorsque le syst\u00e8me est destin\u00e9 \u00e0 un utilisateur grand-public, on pr\u00e9f\u00e9rera proposer une interface graphique plus esth\u00e9tique et intuitive. C&rsquo;est ce que nous allons examiner \u00e0 pr\u00e9sent.<\/p>\n\n\n\n<h2 id=\"T3.6\" class=\"wp-block-heading\">3.6 &#8211; Programme C++ utilisant la biblioth\u00e8que graphique Qt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il existe plusieurs biblioth\u00e8ques d&rsquo;interface graphique fonctionnant sous Linux. Les plus r\u00e9pandues sont probablement GTK et surtout <strong>Qt<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Les images d&rsquo;installation d&rsquo;Eris Linux (que nous avons vues dans le deuxi\u00e8me article de cette s\u00e9rie) proposent deux variantes&nbsp;: <em>graphical<\/em> et <em>headless<\/em> selon la pr\u00e9sence ou non d&rsquo;un \u00e9cran graphique sur les syst\u00e8mes cibles. Dans le cas d&rsquo;un syst\u00e8me <em>graphical<\/em>, la biblioth\u00e8que Qt 6 (6.11 lors de la r\u00e9daction de ces lignes) est install\u00e9e avec un support X11.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si le besoin s&rsquo;en fait sentir, il est possible de cr\u00e9er une image d\u00e9di\u00e9e incorporant une autre version de Qt, ou incorporant GTK, ou encore s&rsquo;appuyant sur Wayland plut\u00f4t que X11. Contactez-nous pour en savoir plus.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">L&rsquo;exemple se trouvant dans le r\u00e9pertoire <code>07-api-test-graphical\/<\/code> propose une interface graphique pour r\u00e9aliser les m\u00eames t\u00e2ches de test de l&rsquo;API Eris Linux que pr\u00e9c\u00e9demment. L&rsquo;organisation de ce r\u00e9pertoire est tr\u00e8s proche de celui de l&rsquo;exemple pr\u00e9c\u00e9dent.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls  07-api-test-graphical\/<\/strong>\nDockerfile  README.md  sources\n\n$ <strong>ls  07-api-test-graphical\/sources\/<\/strong>\napi-test.qss          network-api-window.cpp  update-api-window.cpp\ngpio-api-window.cpp   network-api-window.h    update-api-window.h\ngpio-api-window.h     sbom-api-window.cpp     virtual-keyboard.cpp\ngraphic-api-test.cpp  sbom-api-window.h       virtual-keyboard.h\nlicense-window.cpp    system-api-window.cpp   virtual-keyboard.qss\nlicense-window.h      system-api-window.h     watchdog-api-window.cpp\nlogo.png              time-api-window.cpp     watchdog-api-window.h\nMakefile              time-api-window.h\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le fichier <code>Makefile<\/code> offre la particularit\u00e9 d&rsquo;appeler la commande shell <code><strong>pkg-config<\/strong><\/code> pour obtenir la liste des flags \u00e0 passer au compilateur C++ et la liste des biblioth\u00e8ques \u00e0 lier pour produire le fichier ex\u00e9cutable (outre <code>liberis<\/code> et <code>libcurl<\/code> qui sont explicitement mentionn\u00e9es).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>EXE=graphic-api-test\n\nOBJS=                     \\\n  graphic-api-test.o      \\\n  virtual-keyboard.o      \\\n  gpio-api-window.o       \\\n  license-window.o        \\\n  network-api-window.o    \\\n  sbom-api-window.o       \\\n  system-api-window.o     \\\n  time-api-window.o       \\\n  update-api-window.o     \\\n  watchdog-api-window.o   \\\n\n\nCXXFLAGS += -Wall -Wextra -Os\nCXXFLAGS += $(shell pkg-config --cflags Qt6Widgets)\nCXXFLAGS += -Iliberis\/include\n\nLDFLAGS += -L.\n\nLDLIBS += -leris\nLDLIBS += $(shell pkg-config --libs Qt6Widgets)\nLDLIBS += -lcurl\n\n.PHONY: all\n\nall: $(EXE)\n\n$(EXE): $(OBJS)\n\t$(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)\n\n%.o: %.cpp\n\t$(CXX) -c $(CXXFLAGS) $&lt;\n\n.PHONY: clean\n\nclean:\n\trm -f $(EXE) $(OBJS)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour que l&rsquo;appel de <code>pkg-config<\/code> fonctionne, il faut que cette commande soit install\u00e9e et disponible d\u00e8s la compilation de l&rsquo;image. C&rsquo;est l\u00e0 que la \u00ab\u00a0magie\u00a0\u00bb de Docker buildx prend tout son sens&nbsp;: on ne fait pas de cross-compilation, on compile l&rsquo;application dans un container avec un environnement semblable \u00e0 celui de l&rsquo;ex\u00e9cution finale (dans lequel on ajoute simplement les outils de compilation <code>build-base<\/code>). Voici le <code>Dockerfile<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest AS build\nRUN apk add --no-cache build-base file curl-dev libstdc++ pkgconf qt6-qtbase-dev git ca-certificates\n\nWORKDIR \/src\nARG LIBERIS_REPO=https:\/\/github.com\/Eris-Linux\/liberis.git\nARG LIBERIS_BRANCH=master\nRUN git clone --depth 1 --branch \"${LIBERIS_BRANCH}\" \"${LIBERIS_REPO}\" \/src\/liberis\n\nWORKDIR \/src\/liberis\nRUN make static\nRUN make static_install\n\nWORKDIR \/src\nCOPY sources\/* .\nRUN make\n\n\nFROM alpine:latest\nRUN apk add --no-cache libcurl\nRUN apk add --no-cache qt6-qtbase-x11 qt6-qtdeclarative fontconfig  ttf-dejavu\nCOPY --from=build \/src\/graphic-api-test \/graphic-api-test\nCOPY --from=build \/src\/api-test.qss \/api-test.qss\nCMD &#91;\"\/graphic-api-test\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Apr\u00e8s compilation de l&rsquo;image, il ne faut pas oublier en l&rsquo;installant sur le serveur Eris Linux d&rsquo;activer la coche \u00ab\u00a0Graphical\u00a0\u00bb.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><a href=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-13.png\"><img loading=\"lazy\" decoding=\"async\" width=\"672\" height=\"591\" src=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-13.png\" alt=\"\" class=\"wp-image-7481\" srcset=\"https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-13.png 672w, https:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2026\/08\/screenshot-4-13-300x264.png 300w\" sizes=\"auto, (max-width: 672px) 100vw, 672px\" \/><\/a><figcaption class=\"wp-element-caption\">Fig. 13 &#8211; <em>Option \u00ab\u00a0Graphical Display\u00a0\u00bb d&rsquo;un container<\/em><\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Une fois l&rsquo;image d\u00e9ploy\u00e9e sur une carte cible dot\u00e9e d&rsquo;un affichage graphique, on doit disposer d&rsquo;une interface simple mais esth\u00e9tique. (Suivant la r\u00e9solution de votre afficheur, les r\u00e9sultats peuvent \u00eatre tr\u00e8s variables, voire totalement inutilisables).<\/p>\n\n\n\n<h1 id=\"T4\" class=\"wp-block-heading\">4 &#8211; Programmes en Rust<\/h1>\n\n\n\n<h2 id=\"T4.1\" class=\"wp-block-heading\">4.1 &#8211; Avertissement<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nous allons examiner quelques exemples d&rsquo;utilisation de <strong>Rust<\/strong> dans diff\u00e9rents types de containers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Je dois pr\u00e9ciser que je n&rsquo;ai pas une tr\u00e8s grande exp\u00e9rience en Rust, elle se limite surtout \u00e0 des tutoriaux d&rsquo;apprentissage et des exercices du challenge <em><strong>Advent of Code<\/strong><\/em>. Je n&rsquo;ai pas encore utilis\u00e9 Rust pour des projets clients, embarqu\u00e9s ou non. Je me limiterai donc \u00e0 commenter un exemple simple \u00e9crit par mon coll\u00e8gue Alexandre.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En revanche, nous allons profiter de ces exemples pour voir des containers bas\u00e9s sur d&rsquo;autres arborescences que celle d&rsquo;Alpine.<\/p>\n\n\n\n<h2 id=\"T4.2\" class=\"wp-block-heading\">4.2 &#8211; Compilation d&rsquo;un programme Rust<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le premier exemple avec Rust se trouve dans le r\u00e9pertoire <code>08-hello-world-in-rust\/<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls  08-hello-world-in-rust\/<\/strong>\nCargo.toml  Dockerfile  README.md  src\n\n$ <strong>ls  08-hello-world-in-rust\/src\/<\/strong>\nmain.rs\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nous avons ici un unique fichier source dans <code>src\/<\/code>, dont le contenu est un peu plus complet que les \u00ab\u00a0Hello World!\u00a0\u00bb minimaux pr\u00e9c\u00e9dents&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>use axum::{Router, response::Html, routing::get};\nuse std::time::Duration;\n\nasync fn std_out_hello() {\n    let mut interval = tokio::time::interval(Duration::from_secs(5));\n\n    loop {\n        interval.tick().await;\n        println!(\"Hello, World! (from Rust)\");\n    }\n}\n\n#&#91;tokio::main]\nasync fn main() {\n    tokio::spawn(std_out_hello());\n\n    \/\/ build our application with a route\n    let app = Router::new().route(\"\/\", get(handler));\n\n    \/\/ run it\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\")\n        .await\n        .unwrap();\n    println!(\"listening on {}\", listener.local_addr().unwrap());\n    axum::serve(listener, app).await;\n}\n\nasync fn handler() -&gt; Html&lt;&amp;'static str&gt; {\n    Html(\"&lt;h1&gt;Hello, World! (from Rust)&lt;\/h1&gt;\")\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notre programme utilise l&rsquo;environnement d&rsquo;ex\u00e9cution <strong>Tokio<\/strong> qui lui permet d&rsquo;ex\u00e9cuter une t\u00e2che asynchrone affichant un message sur sa sortie standard toutes les cinq secondes, puis de cr\u00e9er un serveur TCP sur le port 3000 qui affiche une ligne de message HTML quand on se connecte. Pour offrir un serveur HTTP correct (pr\u00eat \u00e0 recevoir la connexion d&rsquo;un navigateur web par exemple) le programme s&rsquo;appuie sur la biblioth\u00e8que Axum.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le fichier <code>Cargo.toml<\/code> repr\u00e9sente le manifeste du programme regroupant les versions de biblioth\u00e8que \u00e0 employer&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;package]\nname = \"example-hello-world\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n&#91;dependencies]\naxum = { version = \"0.8.9\" }\ntokio = { version = \"1.0\", features = &#91;\"full\"] }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enfin un <code>Dockerfile<\/code> va permettre de cr\u00e9er l&rsquo;image de container. Comme d&rsquo;habitude il est construit en deux parties, la premi\u00e8re permet de compiler le programme&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM  alpine:latest AS build\n\nRUN apk add --no-cache build-base file rust cargo\nWORKDIR \/src\nCOPY  .  .\nRUN cargo build  --release\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La seconde partie construit le container d&rsquo;ex\u00e9cution&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:latest\n\nRUN apk add --no-cache libgcc\nCOPY --from=build  \/src\/target\/release\/example-hello-world  \/example-hello-world\nCMD &#91;\"\/example-hello-world\"]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pour la compilation de l&rsquo;application en Rust, on fait appel \u00e0 <code>cargo build<\/code>  avec l&rsquo;option <code>--release<\/code> pour activer l&rsquo;optimisation du code en production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Comme pour les autres exemples, on utilisera<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>$ create-container &lt;architecture&gt; &lt;directory&gt; &lt;container-name&gt;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">pour produire l&rsquo;image, puis on l&rsquo;enverra sur le serveur Eris Linux (en exportant le port TCP 3000) avant de la d\u00e9ployer vers les cartes cibles.<\/p>\n\n\n\n<h2 id=\"T4.3\" class=\"wp-block-heading\">4.3 &#8211; Image <em>Distroless<\/em> pour le container d&rsquo;ex\u00e9cution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nous avons utilis\u00e9 jusqu&rsquo;\u00e0 pr\u00e9sent pour le container produit une image bas\u00e9e sur une distribution Alpine. Celle-ci est tr\u00e8s l\u00e9g\u00e8re mais compl\u00e8te. Elle contient un shell, des utilitaires, des biblioth\u00e8ques suffisantes pour permettre une connexion de l&rsquo;utilisateur et sa navigation sur le syst\u00e8me. Elle contient aussi un gestionnaire de paquets (<code>apk<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ceci n&rsquo;est pas toujours n\u00e9cessaire. On peut par exemple employer une image bas\u00e9e sur \u00ab\u00a0<em><strong>Google Distroless<\/strong><\/em>\u00a0\u00bb : une distribution ultra-minimale centr\u00e9e sur l&rsquo;application unique. Impossible de se connecter (pas de shell), ni d&rsquo;ajouter dynamiquement des packages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour cela la seule modification \u00e0 apporter (voir le sous-r\u00e9pertoire <code>09-hello-rust-distroless\/<\/code>) est de transformer la premi\u00e8re lignes de la premi\u00e8re partie du <code>Dockerfile<\/code> en&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>FROM rust:1.96.0 AS builder<\/strong>\n\nWORKDIR \/app\nCOPY  .  .\nRUN cargo build  --release<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">et la premi\u00e8re ligne de la seconde partie ainsi&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>FROM gcr.io\/distroless\/cc-debian13<\/strong>\n\nCOPY --from=builder  \/app\/target\/release\/example-hello-world  \/example-hello-world\nEXPOSE 3000\nCMD &#91;\"\/example-hello-world\"]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La compilation, le transfert sur le serveur Eris Linux (avec l&rsquo;exportation de port 3000) et le d\u00e9ploiement sont identiques \u00e0 l&rsquo;exemple pr\u00e9c\u00e9dent.<\/p>\n\n\n\n<h2 id=\"T4.4\" class=\"wp-block-heading\">4.4 &#8211; Container avec application unique<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On peut r\u00e9duire encore la taille du container en n&#8217;embarquant plus d&rsquo;arborescence, mais en construisant une image limit\u00e9 \u00e0 l&rsquo;ex\u00e9cutable compil\u00e9.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pour cela on va utiliser un container vide (\u00ab\u00a0<em>scratch<\/em>\u00a0\u00bb dans la terminologie Docker) dans lequel on ne mettra que notre application. Classiquement, les ex\u00e9cutables sous Linux d\u00e9pendent tous de la <code>libC<\/code> qui fournit les points d&rsquo;acc\u00e8s aux appels syst\u00e8me, mais comme on n&rsquo;installera aucune biblioth\u00e8que dans l&rsquo;arborescence de l&rsquo;image, il faudra compiler notre application avec un <em>link<\/em> statique.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ceci s&rsquo;obtient en modifiant l\u00e9g\u00e8rement la premi\u00e8re partie du <code>Dockerfile<\/code> :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM rust:1.96.0 AS builder\n\nWORKDIR \/app\nCOPY  .  .\n<strong>ARG FULLARCH\nRUN rustup target add \"${FULLARCH}\"\nRUN cargo build  --target \"${FULLARCH}\" --release<\/strong>\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">pour la seconde partie du <code>Dockerfile<\/code>, on part d&rsquo;une distribution vide et n&rsquo;installe que l&rsquo;ex\u00e9cutable produit&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>FROM scratch<\/strong>\nARG FULLARCH\nCOPY --from=builder  \/app\/target\/${FULLARCH}\/release\/example-hello-world  \/example-hello-world\nEXPOSE 3000\nCMD &#91;\"\/example-hello-world\"]\n<\/code><\/pre>\n\n\n\n<h2 id=\"T4.5\" class=\"wp-block-heading\">4.5 &#8211; Comparaisons<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Les trois containers pr\u00e9c\u00e9dents ont \u00e9t\u00e9 produits avec des m\u00e9thodes diff\u00e9rentes. Voici les r\u00e9sultats en terme de taille de fichier image de container (d\u00e9compress\u00e9e)&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Container incluant une distribution <em>Alpine<\/em> : 11 Mo.<\/li>\n\n\n\n<li>Container bas\u00e9 sur une distribution <em>Distroless<\/em> : 40 Mo.<\/li>\n\n\n\n<li>Container embarquant uniquement l&rsquo;application&nbsp;: 2 Mo.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Il est un peu surprenant <em>a priori<\/em> de voir que le container <em>Distroless<\/em> est plus volumineux que celui bas\u00e9 sur <em>Alpine<\/em>. Il faut toutefois savoir que la libC utilis\u00e9e par <em>Alpine<\/em> est la biblioth\u00e8que <em>Musl<\/em> (tr\u00e8s optimis\u00e9e pour la r\u00e9duction de taille) et celle employ\u00e9e par <em>Distroless<\/em> est <em>GlibC<\/em> (tr\u00e8s riche et compl\u00e8te mais plut\u00f4t volumineuse). L&rsquo;int\u00e9r\u00eat de <em>Distroless<\/em> sera surtout une am\u00e9lioration de la s\u00e9curit\u00e9 par r\u00e9duction du p\u00e9rim\u00e8tre d&rsquo;attaque.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c0 l&rsquo;extr\u00eame, le container n&#8217;embarquant que l&rsquo;application permet d&rsquo;obtenir une r\u00e9duction impressionnante de la taille de l\u2019image.<\/p>\n\n\n\n<h1 id=\"T5\" class=\"wp-block-heading\">5 &#8211; Exemple en Python<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Pour finir nous allons examiner un dernier container, \u00e9crit par ChatGPT en Python.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il s&rsquo;agit \u00e0 nouveau d&rsquo;un serveur affichant un message \u00ab\u00a0Hello World!\u00a0\u00bb, mais cette fois le service est HTTPS avec gestion d&rsquo;un certificat g\u00e9n\u00e9r\u00e9 \u00e0 la cr\u00e9ation de l&rsquo;image. Voici le contenu du r\u00e9pertoire concern\u00e9&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ <strong>ls  11-hello-world-in-python\/<\/strong>\nDockerfile  hello-world-in-python.py  README.md<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le script est un peu plus complet que ceux que nous avons vus pr\u00e9c\u00e9demment. Il s&rsquo;appuie sur les modules Python <code>http.server<\/code> et <code>ssl<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n#\n# Simple Python HTTPS \"Hello World\" example for Eris Linux containers.\n#\n# ChatGPT 2026\n#\n# License: MIT\n#\n\nimport ssl\nfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServer\n\nHOST = \"0.0.0.0\"\nPORT = 443\n\nCERT_FILE = \"\/etc\/eris-linux\/ssl\/hello-world.crt\"\nKEY_FILE = \"\/etc\/eris-linux\/ssl\/hello-world.key\"\n\n\nclass HelloWorldHandler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        print(\"Hello World\", flush=True)\n\n        body = b\"\"\"&lt;!doctype html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"utf-8\"&gt;\n  &lt;title&gt;Eris Linux Python container&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h1&gt;Hello World&lt;\/h1&gt;\n  &lt;p&gt;This page is served by a Python HTTPS server running in an Eris Linux container.&lt;\/p&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n\"\"\"\n\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"text\/html; charset=utf-8\")\n        self.send_header(\"Content-Length\", str(len(body)))\n        self.end_headers()\n        self.wfile.write(body)\n\n    def log_message(self, fmt, *args):\n        print(\"%s - %s\" % (self.address_string(), fmt % args), flush=True)\n\n\ndef main():\n    print(\"Hello World (from Python)\", flush=True)\n    print(f\"Starting HTTPS server on port {PORT}\", flush=True)\n\n    server = ThreadingHTTPServer((HOST, PORT), HelloWorldHandler)\n\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    context.load_cert_chain(certfile=CERT_FILE, keyfile=KEY_FILE)\n\n    server.socket = context.wrap_socket(server.socket, server_side=True)\n\n    server.serve_forever()\n\n\nif __name__ == \"__main__\":\n    main()\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le fichier <code>Dockerfile<\/code> qui l&rsquo;accompagne est en deux parties. La premi\u00e8re partie g\u00e9n\u00e8re le certificat dans un container fonctionnant nativement sur la plateforme de d\u00e9veloppement. Cette op\u00e9ration \u00e9tant ind\u00e9pendante de l\u2019architecture cible, elle ne n\u00e9cessite aucune \u00e9mulation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cette m\u00e9thode convient \u00e0 cet exemple de d\u00e9monstration. Sur un produit r\u00e9el, chaque \u00e9quipement devrait disposer de son propre certificat et de sa propre cl\u00e9 priv\u00e9e, g\u00e9n\u00e9r\u00e9s ou provisionn\u00e9s individuellement.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ARG ALPINE_VERSION=3.22\n\nFROM --platform=$BUILDPLATFORM alpine:${ALPINE_VERSION} AS certificates\nRUN apk add --no-cache openssl\nRUN mkdir -p \/out \\\n &amp;&amp; openssl req                                                   \\\n      -x509                                                       \\\n      -newkey rsa:2048                                            \\\n      -nodes                                                      \\\n      -keyout \/out\/hello-world.key                                \\\n      -out \/out\/hello-world.crt                                   \\\n      -days 3650                                                  \\\n      -subj \"\/CN=eris-linux-python-hello-world\"                   \\\n &amp;&amp; chmod 600 \/out\/hello-world.key\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La seconde partie du <code>Dockerfile<\/code> installe les certificats et le script dans l&rsquo;image de container finale&nbsp;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FROM alpine:${ALPINE_VERSION}\nRUN apk add --no-cache python3\n\nCOPY --from=certificates \/out\/hello-world.crt \/etc\/eris-linux\/ssl\/hello-world.crt\nCOPY --from=certificates \/out\/hello-world.key \/etc\/eris-linux\/ssl\/hello-world.key\nCOPY hello-world-in-python.py \/hello-world-in-python.py\nRUN chmod +x \/hello-world-in-python.py\nEXPOSE 443\n\nCMD &#91; \"python3\", \"-u\", \"\/hello-world-in-python.py\" ]\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Apr\u00e8s g\u00e9n\u00e9ration de l&rsquo;image, enregistrement sur le serveur Eris Linux (en exportant le port TCP 443 &#8211; celui de HTTPS), et d\u00e9ploiement sur une cible, on peut se connecter en utilisant l&rsquo;URL <code>https:\/\/&lt;target-address><\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Comme c&rsquo;est souvent le cas en embarqu\u00e9 industriel, on peut consid\u00e9rer que les cartes cibles sont install\u00e9es sur un sous-r\u00e9seau d\u00e9di\u00e9, sans acc\u00e8s direct \u00e0 Internet. Pour cet exemple autonome, nous utilisons donc un <strong>certificat autosign\u00e9<\/strong>. Il est donc normal que le navigateur affiche un avertissement lors de la premi\u00e8re connexion \u00e0 notre cible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dans un produit industriel, on pourra pr\u00e9f\u00e9rer un certificat propre \u00e0 chaque \u00e9quipement, \u00e9ventuellement d\u00e9livr\u00e9 par une autorit\u00e9 de certification interne.<\/p>\n\n\n\n<h1 id=\"T6\" class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Nous avons pu \u00e9tudier dans cet article plusieurs exemples de code applicatif h\u00e9berg\u00e9 dans des containers pour Eris Linux qui couvrent une bonne partie des cas d&rsquo;usage dans le domaine embarqu\u00e9&nbsp;:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>l&rsquo;installation de <strong>services standards de Linux<\/strong> (comme OpenSSH) \u00e9ventuellement avec une configuration sp\u00e9cifique (comme Nginx),<\/li>\n\n\n\n<li>l&rsquo;installation de <strong>programmes interpr\u00e9t\u00e9s<\/strong> (scripts shell et Python), pouvant m\u00eame acc\u00e9der directement aux ressources mat\u00e9rielles du syst\u00e8me,<\/li>\n\n\n\n<li>la mise en \u0153uvre d&rsquo;<strong>applications compil\u00e9es <\/strong>(en C, en C++, en Rust), utilisant \u00e9ventuellement une biblioth\u00e8que graphique (Qt),<\/li>\n\n\n\n<li>l&rsquo;appel des fonctions de l&rsquo;<strong>API Eris Linux<\/strong>, depuis une page Web (API Rest) ou depuis un programme compil\u00e9 (API <code>liberis<\/code>),<\/li>\n\n\n\n<li>utilisation de containers embarquant une <strong>distribution Alpine, Google Distroless<\/strong> ou encore ne contenant que l&rsquo;<strong>ex\u00e9cutable applicatif<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Dans le prochain article nous allons examiner plus pr\u00e9cis\u00e9ment l&rsquo;API de programmation propos\u00e9e par Eris Linux.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nous recherchons actuellement des entreprises partenaires souhaitant exp\u00e9rimenter Eris Linux sur un cas d\u2019usage r\u00e9el. Ces premiers utilisateurs pourront b\u00e9n\u00e9ficier de conditions commerciales avantageuses et leurs retours d\u2019exp\u00e9rience contribueront directement \u00e0 l\u2019\u00e9volution du projet.<\/p>","protected":false},"excerpt":{"rendered":"<p>Dans les articles pr&eacute;c&eacute;dents de cette s&eacute;rie consacr&eacute;e &agrave; Eris Linux, nous avons commenc&eacute; par examiner les principes et les avantages d&rsquo;Eris. Nous avons ensuite &eacute;tudi&eacute; comment installer Eris Linux sur une carte standard (un Raspberry Pi 5) puis comment d&eacute;ployer depuis le Device Manager des containers de test sur notre &eacute;quipement. Nous allons voir [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,27,8,11],"tags":[],"class_list":["post-7318","post","type-post","status-publish","format-standard","hentry","category-embarque","category-eris-linux","category-linux-2","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/7318","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=7318"}],"version-history":[{"count":106,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/7318\/revisions"}],"predecessor-version":[{"id":7838,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/7318\/revisions\/7838"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=7318"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=7318"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=7318"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}