Bonne année ! Oui, je viens de me souvenir que j’avais un blog, vous inquiétez pas, y a eu beaucoup de changements, mais tout va bien se passer.


** Attention, cet article n’est pas holistique, si vous faites du déploiement d’environnements de production sous Docker (et autres technos similaires), le contenu peut choquer. /j **

** Nous vous invitons donc à garder vos remarques personnelles sur nos méthodes de déploiements pour… vous. Merci bien ! **


Le besoin

Ayant de plus en plus d’application à gérer, tant personnellement que pour le compte d’Anjara, gérer des installations manuelles est devenu vite chronophage et usant.

La solution ? Docker Monter une stack CI via le GitLab de l’association pour construire des paquets des applications concernées.

Quoi qu’on pense de comment il faudrait déployer une application en 2026, à moins de faire du LFS, on va forcément un moment ou un autre, passer par le gestionnaire de paquets de sa distribution.

Déployant essentiellement du Debian, dans mon cas, cela va donc être via APT.

https://www.xkcd.com/1654/

https://www.xkcd.com/1654/

Pré-requis

Pour mettre en œuvre cette petite fabrique, il va falloir plusieurs briques.

  • Un serveur Git : GitLab dans notre cas.
  • Une stack pour la CI : On réutilise directement la CI de GitLab.
  • Un runner quelconque : Vous connaissez la chanson.
  • Une clé GPG : Pour signer les paquets
  • De quoi héberger le(s) répo(s) : Dans notre cas GitLab Pages

La mise en œuvre

Je vais prendre l’exemple de l’application Vaultwarden, mais c’est globalement adaptable à plus ou moins n’importe quoi.

Dans le repo il y a 3 composants distincts identifiables :

  • Le dossier contenant les données nécessaires pour le paquet Debian.
  • Le fichier .gitlab-ci.yml qui contiendra les instructions de construction.
  • Le script python generate_indexes.py qui permettra de générer un joli index navigable.

Un paquet Debian, c’est pas ce qu’il y a de plus compliqué à faire.

.
├── generate_indexes.py
└── vaultwarden
    ├── DEBIAN
    │   ├── conffiles
    │   ├── control
    │   ├── dirs
    │   ├── postinst
    │   └── preinst
    └── usr
        └── lib
            └── systemd
                └── system
                    └── vaultwarden.service

Le dossier vaultwarden, qui dans l’absolu pourrait s’appeler n’importe comment, est à considérer comme le rootfs de la machine qui installera notre paquet. En dehors du dossier DEBIAN, l’intégralité du contenu sera décompressée tel que.

Ainsi, on peut mettre notre fichier de configuration par défaut dans vaultwarden/etc/vaultwarden.env par exemple, ou bien notre unité systemd dans vaultwarden/usr/lib/systemd/system/vaultwarden.service ou encore notre binaire construit dans vaultwarden/usr/bin/vaultwarden.

La gestion des paramètres du paquet se fait dans le dossier DEBIAN. On trouvera notamment :

  • conffiles : Liste des fichiers isolés de l’application. (Fichiers de configurations, unités systemd, etc…)
  • control : Contiens toutes les méta-données du paquet, comme son nom, sa version, son architecture, ses dépendances, son mainteneur, etc…
  • dirs : Liste des dossiers “gérés” par l’application.
  • postinst : Script bash normalisé qui sera exécuté après l’installation du paquet.
  • preinst : Script bash normalisé qui sera exécuté avant l’installation du paquet.

Une fois qu’on a tout ce beau monde de prêt, on peut passer à la partie CI.

variables:
  DEBIAN_VERSION: "trixie"
  VAULTWARDEN_VERSION: "1.35.1"
  WEBVAULT_VERSION: "2025.12.1"

image: debian:${DEBIAN_VERSION}

stages:
  - build_backend
  - package
  - test
  - pages

Dans mon cas, je vais construire avec une image Debian Trixie comme base, je déclare en variable la version de vaultwarden et de webvault souhaitée. Ma recette est en 4 étapes, build_backend pour construire le binaire de vaultwarden, package pour faire la construction du package debian, test pour vérifier que le package s’installe correctement et pages pour publier le paquet.

Je vais omettre la partie build_backend qui globalement sera très spécifique à l’application, ce qu’il y a à retenir est tout simplement que vaultwarden est compilé from source, et le binaire résultant de ladite compilation est publié en artifact pour l’étape suivante.

En viens la partie package

package:vaultwarden:
  stage: package
  before_script:
    - find vaultwarden -type d -exec chmod 755 {} \;
    - find vaultwarden -type f -not -name postinst -exec chmod 644 {} \;
    - find vaultwarden -type f -name postinst -exec chmod 755 {} \;
    - find vaultwarden -type f -name preinst -exec chmod 755 {} \;
    - mkdir -p vaultwarden/usr/bin/
    - mkdir -p vaultwarden/etc
    - mkdir -p vaultwarden/var/lib/vaultwarden
    - DEBIAN_FRONTEND=noninteractive apt -qy update > /dev/null
    - DEBIAN_FRONTEND=noninteractive apt -qyy install wget > /dev/null
  script:
    - mkdir -p build/
    - sed -i 's/%%VAULTWARDEN_VERSION%%/'${VAULTWARDEN_VERSION}'/g' vaultwarden/DEBIAN/control
    - cd build/
    - wget https://github.com/dani-garcia/bw_web_builds/releases/download/v${WEBVAULT_VERSION}/bw_web_v${WEBVAULT_VERSION}.tar.gz
    - tar xvf bw_web_v${WEBVAULT_VERSION}.tar.gz
    - mv web-vault ../vaultwarden/var/lib/vaultwarden/
    - cd ..
    - mv dist/vaultwarden vaultwarden/usr/bin/
    - mv dist/vaultwarden.env vaultwarden/etc/
    - dpkg-deb -b vaultwarden/
    - mv vaultwarden.deb dist/
  artifacts:
    when: on_success
    expire_in: 1 week
    paths:
      - dist/*

Ici en before_script on va mettre d’équerre toutes les permissions et créer tous les dossiers qu’il nous manque. On en profite aussi pour installer dans le conteneur de construction, les dépendances qu’il nous faudrait.

Dans la partie script, on va récupérer web_vault qu’on va décompresser et mettre dans /var/lib/vaultwarden, déplacer le binaire de vaultwarden dans /usr/bin et la configuration par défaut dans /etc. Et ensuite avec dpkg-deb on va construire le paquet et déplacer le résultat dans dist/ dont le contenu sera publié comme artifact pour la partie suivante : le test.

test:
  stage: test
  script:
    - DEBIAN_FRONTEND=noninteractive apt -qy update > /dev/null
    - DEBIAN_FRONTEND=noninteractive apt install ./dist/vaultwarden.deb -qy

Ici on va simplement s’assurer que le package s’installe sans faire d’erreur. Mais globalement, on pourrait tester bien plus de trucs. Enfin, si le test revient sans erreur, on peut publier le paquet via pages.

pages:
  stage: pages
  before_script:
    - DEBIAN_FRONTEND=noninteractive apt update -qy > /dev/null
    - DEBIAN_FRONTEND=noninteractive apt install wget gpg gnupg2 python3 -qy
    - mkdir -p /usr/share/keyrings
    - wget -O /usr/share/keyrings/aptly.asc https://www.aptly.info/pubkey.txt
    - |
      cat <<EOF | tee /etc/apt/sources.list.d/aptly.sources > /dev/null
      Types: deb
      URIs: http://repo.aptly.info/release
      Suites: ${DEBIAN_VERSION}
      Components: main
      Signed-By: /usr/share/keyrings/aptly.asc
      EOF
    - DEBIAN_FRONTEND=noninteractive apt update -qy > /dev/null
    - DEBIAN_FRONTEND=noninteractive apt install aptly -qy
    - gpg --import --no-tty --batch --yes ${GPG_PRIVATE_KEY}
    - aptly repo create -distribution=nodistro -component=main public
    - echo -e "use-agent\npinentry-mode loopback" > ~/.gnupg/gpg.conf
    - echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf
  only:
    - main
  script:
    - aptly repo add public ./dist/
    - aptly publish repo -batch -passphrase="${GPG_PASSPHRASE}" -gpg-key="${GPG_KEY_ID}" public
    - mv ~/.aptly/public .
    - gpg --armor --output public/gpg.key --export ${GPG_KEY_ID}
    - python3 generate_indexes.py
  artifacts:
    paths:
      - public

Ici on va se baser sur Aptly pour générer notre dépôt Debian en lui donnant en entrée le dossier qui servira de repo, chez nous public, et le dossier où il doit récupérer le ou les .deb, chez nous dist. On signe tout ce beau monde avec une clé GPG qu’on met également à dispos, on génère l’index avec notre script python et le tour est joué, nous avons à présent un dépôt Debian accessible via GitLab Pages.

Pour le cas de Vaultwarden, c’est en ligne ici.

On peut donc sur notre machine cible ajouter le repo…

skid@vaultwarden ~ cat /etc/apt/sources.list.d/vaultwarden.sources
Types: deb
URIs: https://anjara.pages.anjara.eu/deb-packages/vaultwarden
Suites: nodistro
Components: main
Signed-By: /usr/share/keyrings/anjara-archive-keyring.gpg

Télécharger la clé GPG associée…

skid@vaultwarden ~ sudo wget https://anjara.eu/anjara-archive-keyring.gpg -O /usr/share/keyrings/anjara-archive-keyring.gpg

Et mettre à jour le cache d’APT.

skid@vaultwarden ~ sudo apt update
Atteint : 1 http://ftp.fr.debian.org/debian trixie InRelease
Atteint : 2 http://ftp.fr.debian.org/debian trixie-updates InRelease        
Atteint : 3 http://security.debian.org trixie-security InRelease                                                                                                      
Atteint : 4 http://apt.postgresql.org/pub/repos/apt trixie-pgdg InRelease                                                                                             
Atteint : 5 https://anjara.pages.anjara.eu/deb-packages/vaultwarden nodistro InRelease
Tous les paquets sont à jour.

Et maintenant on peut faire un apt install vaultwarden et on aura un beau paquet tout propre d’installé.

Et pour les mises à jour ?

Et c’est justement là que tout ce beau monde fait sens : c’est uniquement des variables à éditer.

variables:
  DEBIAN_VERSION: "trixie"
  VAULTWARDEN_VERSION: "1.35.1"
  WEBVAULT_VERSION: "2025.12.1"

J’édite donc VAULTWARDEN_VERSION et WEBVAULT_VERSION, je commit et le paquet dans sa nouvelle version sera construit, publié et disponible via apt upgrade sur les machines cibles !

Mais du coup, on peut vraiment packager n’importe quoi ?

En pratique, oui, on POURRAIT packager n’importe quoi.

La question étant plutôt, est ce qu’on VEUT packager n’importe quoi ?

Dans les divers paquets que j’ai pu réaliser, ils ont tous un point commun : Il y a que des éléments relativement bien contenus.

On a une structure très simple, à base d’un binaire, une unité systemd, éventuellement un fichier de configuration, et ça s’arrête là.

Malheureusement, dans certaines applications modernes, notamment développées en langages dit « interprété » (Python, NodeJS, etc…), on se retrouve avec une quantité de dépendances externes (avec souvent, des versions hyper spécifiques) absolument délirantes.

  • Paperless-NGX (Python) : 50+ dépendances externes
  • Misskey (NodeJS) : 150+ dépendances externes
  • Mastodon (Ruby) : 100+ dépendances externes

On pourrait techniquement s’amuser à construire ça, si on veut faire proprement, il faudrait s’amuser à ou :

  • Prier que quelqu’un a déjà publié un paquet Debian de chaque dépendance et sous dépendance dans la bonne version
  • Construire et publier toutes les dépendances et sous dépendances en plus du logiciel de base.

Car packager toutes les dépendances dans le même paquet, ça serait un peu à l’encontre même du principe.

Mais, du coup, si on veut faire proprement, on arrive typiquement à :

Est-ce que cela en vaut la peine ?

https://www.xkcd.com/1205/

https://www.xkcd.com/1205/

Ma conclusion à l’instant T est que, pour ces applications, non. Celà ne veut pas dire que je prendrais pas un jour le temps de les construire proprement, mais que simplement, aujourd’hui, le temps (et les nerfs) que je sauverais en les construisant n’est pas assez conséquent pour que ça en vaille la peine.

Conclusion ?

Ça fait 4 mois depuis le premier paquet que j’ai fait, ça tourne globalement bien, rien à redire. Si vous voulez utiliser les paquets qu’on a pu créer ou vous en inspirer, la liste des projets est disponible sur notre GitLab :)

Notez simplement qu’ils sont principalement conçus pour les besoins de l’association et sont fournis publiquement sans aucune garantie.

Sur ce, on se retrouve dans 3 ans ! (ou avant, si j’ai la motivation)