Déploiement Rocky / RHEL avec Podman Quadlet¶
Cette page complète la procédure standard (Installation depuis l'image
ou Installation souverain-hybride) pour les
opérateurs qui veulent abandonner podman-compose au profit de
Podman Quadlet (unités systemd .container, .network, .volume)
sur un host Rocky Linux 10 / RHEL 9+ / Alma 9+ avec SELinux Enforcing.
Pour qui ? Profil sysadmin habitué à systemd. Si vous découvrez Podman, restez sur
podman-compose— c'est plus simple et c'est ce que le bundle officiel ship par défaut.
Quadlet vs podman-compose :
podman-compose |
Quadlet | |
|---|---|---|
| Démarrage des conteneurs | manuel après reboot | auto via systemd |
| Gestion logs | podman logs <ctr> |
journalctl --user -u myeline-web |
| Healthchecks | définis dans le compose | définis dans le .container ou via systemd |
| Restart policy | docker-compose-style | systemd natif (Restart=always, RestartSec=10) |
| Multi-app sur le même host | risque de collisions de noms / ports | isolement systemd par unité |
Auto-update via auto-update policy |
non supporté | natif (AutoUpdate=registry) |
Pré-requis spécifiques¶
-
Linger activé pour l'utilisateur Podman rootless :
Sans ça, vos containers tombent dès que la session SSH se ferme. -
ip_unprivileged_port_start=80pour binder 80/443 en rootless : -
podman 4.6+ (Rocky 10 ship 5.x — OK)
Génération des fichiers Quadlet à partir du compose¶
Le bundle officiel Myeline embarque docker-compose.yml + variantes
sovereign/hybrid. Quadlet n'a pas d'import direct — vous devez écrire
les unités manuellement, en miroir des services du compose.
Localisation des unités utilisateur : ~/.config/containers/systemd/
Pattern de conversion service par service :
# ~/.config/containers/systemd/myeline-web.container
[Unit]
Description=Myeline web (Flask)
After=network-online.target myeline-mariadb.service myeline-redis.service
[Container]
ContainerName=myeline-web
Image=rg.fr-par.scw.cloud/myeline/web:v1.0.2
Environment=FLASK_ENV=production
EnvironmentFile=%h/myeline/.env
PublishPort=5000:5000
Network=myeline-net.network:alias=web
Volume=%h/myeline/uploads:/app/uploads:Z
Volume=%h/myeline/data:/app/data:Z
Volume=%h/myeline/logs:/app/logs:Z
Volume=%h/myeline/app/myeline_license_pubkey.pem:/app/app/myeline_license_pubkey.pem:ro,Z
HealthCmd=curl -fsS -o /dev/null --max-time 5 http://127.0.0.1:5000/healthz
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
HealthStartPeriod=20s
[Service]
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
Activer + démarrer :
systemctl --user daemon-reload
systemctl --user start myeline-web
systemctl --user enable myeline-web
SELinux Enforcing — pièges connus¶
1. Le suffixe :Z n'est pas idempotent en rootless¶
Sur RHEL/Rocky, les options de remontage Volume :Z (label privé
conteneur) et :z (label partagé) changent la catégorie MCS à
chaque restart du conteneur. Conséquence : un volume formaté la
veille devient illisible aujourd'hui parce que les inodes ont une
catégorie qui ne matche plus le label courant du process. Symptôme
typique : MariaDB qui boucle au démarrage avec « cannot read
mysql table », ou Ollama qui ne retrouve plus ses modèles.
Recommandation pour les services à datadir persistant
(mariadb, ollama, redis si AOF activé) :
-
Pré-labéliser le dossier hôte une seule fois :
-
Désactiver le relabel dans l'unité :
-
Pour les volumes éphémères ou tampons (uploads, logs applicatifs), garder
:Zest OK — pas de drift puisque les fichiers sont régulièrement réécrits.
2. socket-proxy : AVC denial sur connectto¶
Le proxy tecnativa/docker-socket-proxy tourne en HAProxy qui doit
établir une connexion vers le socket Podman du host. Avec le type de
process par défaut, SELinux bloque ce connectto. Symptôme : tous
les appels API Podman du conteneur web retournent 503.
Fix dans myeline-socket-proxy.container :
[Container]
SecurityLabelType=container_runtime_t
# (PAS SecurityLabelDisable=true — on garde le type, on change juste sa classe)
3. Vérifier les denials¶
Ne jamais passer SELinux en Permissive ou Disabled comme
contournement — corrigez le contexte/booléen à la place.
UIDs en rootless — calculer le mapping host ↔ container¶
En Podman rootless, les UIDs internes des conteneurs sont remappés vers
des UIDs hôte via /etc/subuid. Pour préparer un bind-mount avec les
bonnes permissions (cas typique : chown du datadir mariadb avant le
1er boot), vous devez calculer le host UID correspondant.
Formule :
Récupérer subuid_start depuis /etc/subuid :
Exemples concrets :
| Service | Container UID | Calcul (subuid_start=589824) | Host UID |
|---|---|---|---|
mariadb (mysql user) |
999 | 589824 + 999 - 1 | 590822 |
redis (redis user) |
999 | 589824 + 999 - 1 | 590822 |
Myeline web (appuser) |
1000 | 589824 + 1000 - 1 | 590823 |
Application typique avant 1er boot mariadb :
ACL pour les writes côté appuser¶
Le user appuser dans le conteneur (host UID = subuid_start + 999)
doit pouvoir écrire dans ~/myeline/data/ pour data/backups/,
data/cache/, etc. Si vous gardez ce dossier owned par votre user
host (typiquement UID 1001), l'écriture est refusée.
Solution recommandée : ACL POSIX plutôt que chown récursif (qui
casserait data/mysql/ et data/ollama/ qui ont leurs propres
ownerships) :
sudo dnf install -y acl # si pas déjà installé
APPUSER_HOST_UID=$((SUBUID_START + 999))
# Permissions courantes (les fichiers existants)
sudo setfacl -m u:${APPUSER_HOST_UID}:rwx ~/myeline/data
# Permissions héritées (les fichiers à venir)
sudo setfacl -d -m u:${APPUSER_HOST_UID}:rwx ~/myeline/data
Vérifier que les ACL sont posées :
Healthchecks avec curl (pas wget)¶
L'image Myeline embarque curl mais pas wget. Si votre unité
Quadlet a un HealthCmd=wget -qO- ..., le healthcheck plante et le
service est marqué unhealthy en boucle. Utilisez :
/healthz répond 200 immédiatement (liveness uniquement) — c'est
celui à privilégier pour les healthchecks. /health fait des
roundtrips DB + Redis + Ollama et peut renvoyer 503 en cas de
ralentissement temporaire d'une dépendance.
DNS aliases dans Quadlet¶
Pour pouvoir préfixer vos ContainerName= (ex. myeline-mariadb)
tout en gardant les hostnames courts dans le .env de l'app (ex.
DATABASE_URL=...@mariadb:3306/...), déclarez un alias DNS sur le
network :
# ~/.config/containers/systemd/myeline-mariadb.container
[Container]
ContainerName=myeline-mariadb
Network=myeline-net.network:alias=mariadb
Sans cet alias, le conteneur web cherche mariadb en DNS et ne
trouve que myeline-mariadb. Symptôme : erreur de connexion DB au
boot du web.
Caddy comme reverse-proxy HTTPS¶
Healthcheck local¶
Si votre Caddyfile applique un redirect HTTPS global, un
healthcheck en HTTP sur http://localhost/ se prend un 308. Le
healthcheck conteneur tombe en unhealthy permanent.
Solution : exposer un endpoint HTTP local non-redirigé dans le Caddyfile :
Puis dans le .container Caddy :
www. optionnel¶
Si vous déclarez :
…mais que le DNS de www.myeline.acme.local n'existe pas, Let's
Encrypt échoue sur le défi HTTP-01 pour www à chaque
renouvellement, log une erreur en boucle, et obtient quand même le
cert principal (Caddy gère la dégradation). Pour éviter le bruit,
séparez en deux blocs :
myeline.acme.local {
reverse_proxy 127.0.0.1:5000
}
# Décommentez si vous avez créé le A/CNAME DNS de www
# www.myeline.acme.local {
# redir https://myeline.acme.local{uri}
# }
Auto-update via Podman¶
Quadlet supporte l'auto-update natif (équivalent Watchtower) :
Puis activez le timer système :
Par défaut, le timer fire une fois par jour. Il pull la dernière
version du tag spécifié, et si le digest a changé, recrée le
conteneur. Attention : si vous pinnez un tag spécifique
(ex. :v1.0.2), il n'y a auto-update que si Myeline re-publie sous
ce tag — ce qui n'arrive pas pour les tags semver immuables. Pour
suivre les releases automatiquement, utilisez :latest ou un tag
moving (:v1), au prix de perdre le contrôle du moment d'upgrade.
Logs et debug¶
# Suivre les logs en direct
journalctl --user -u myeline-web -f
# Logs des 10 dernières minutes, tous services Myeline
journalctl --user --since "10 minutes ago" -u "myeline-*"
# État détaillé d'un service
systemctl --user status myeline-mariadb
# Inspecter un AVC SELinux
sudo ausearch -m AVC -ts recent | sudo audit2why
Migration depuis podman-compose existant¶
Si vous tournez déjà sur compose et voulez basculer en Quadlet sans perdre vos données :
- Identifiez vos volumes bind-mount sur l'host (
./data,./uploads,./logstypiquement, depuisdocker-compose.yml). - Arrêtez la stack compose :
podman-compose down(sans-v!). - Pré-labélisez les datadirs (cf. section SELinux).
- Écrivez vos unités Quadlet pointant vers les mêmes paths sur l'host.
systemctl --user daemon-reload && systemctl --user start myeline-mariadb- Vérifiez les healthchecks + logs avant de démarrer les autres.