Gestión térmica en notebook clamshell con RAPL y tuned

Tapa cerrada, monitor portátil apoyado encima, notebook como CPU box debajo de todo: lo que se llama modo clamshell —la notebook cerrada como una almeja, usada solo como unidad de cómputo con monitor externo—. El objetivo: evitar que suba la temperatura dañando el hardware.

El problema, brevemente

Los notebooks toman aire por abajo y/o expulsan calor por arriba del teclado. Un monitor portátil apoyado en clamshell rompe ese flujo. Como no se puede recuperar la disipación sin tocar hardware, lo que queda es bajar voluntariamente el techo de potencia para que el CPU jamás llegue a temperaturas de throttling.

Mejor 12W tibios sostenidos que un loop entre 28W ardiendo y 5W throttleado.

Eso es todo. El resto del post es la pelea para implementarlo en Fedora 43 (el setup no cambió en Fedora 44).

Las tres palancas

Linux expone tres puntos donde tocar, y cada uno opera en un plano distinto del sistema:

RAPL (Running Average Power Limit). El CPU Intel se autoimpone límites de potencia: PL1 sostenido, PL2 boost. Están en /sys/class/powercap/intel-rapl/. Si los bajás, el CPU físicamente no puede consumir más. Es el techo duro, aplicado por el hardware del procesador (el PCU, Power Control Unit): no depende ni del scheduler ni del usuario.

tuned. Daemon de gestión de perfiles de energía. Desde Fedora 41 es el default del sistema (vía tuned-ppd), reemplazando a power-profiles-daemon. Maneja governor, EPP, runtime PM, sysctls, sysfs. No baja literalmente el consumo: configura los subsistemas del kernel para que ellos lo hagan. Opera en el plano de las políticas, no del hardware — por eso no se solapa con RAPL.

thermald. Reactivo: lee zonas térmicas y aplica medidas correctivas cuando se calienta. En este equipo nunca funcionó, y de eso también va el post.

RAPL, primer round

Bajar PL1 a 12W y PL2 a 20W es un echo a un archivo en /sys/:

echo 12000000 | sudo tee /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw

Tres segundos, listo. Después de verificar que el valor quedó aplicado y que las temperaturas en stress test caían a una zona razonable, vino la primera trampa con la que choco cada tanto y siempre me tomo unos minutos para acordarme:

/sys no persiste. Lo que escribís ahí desaparece al reboot.

Solución: un service unit propio (rapl-limits.service) que reaplica los valores al boot. Heredoc, daemon-reload, enable --now. Hecho.

thermald, el daemon zombi

Acá viene la parte que me costó descubrir.

Habilito thermald, escribo un /etc/thermald/thermal-conf.xml custom, lo arranco. systemctl status lo reporta activo. ps -ef | grep thermald muestra el proceso vivo. Todo bien.

Solo que no estaba haciendo absolutamente nada.

Días después, ya con el sistema funcionando bien y con thermald deshabilitado por sospechoso, abro journalctl -u thermald para confirmar la sospecha. Encuentro esto, mezclado entre los logs:

thermald[1010]: Thermal DTS: No coretemp sysfs found
thermald[1010]: Thermal DTS or hwmon: No Zones present Need to configure manually
thermald[1010]: XML zone: invalid sensor type x86_pkg_temp
thermald[1010]: Zone update failed: unable to bind

Una sola causa raíz, dos síntomas en el log:

  1. No había un sensor térmico del CPU que descubrir. El módulo que expone la temperatura de paquete no estaba cargado (No coretemp sysfs found), así que thermald arrancó sin ninguna zona del CPU a la vista. Lo venía diciendo desde el primer boot del histórico, mucho antes de que yo empezara a tocar nada.
  2. El invalid sensor type x86_pkg_temp no significa lo que parece. x86_pkg_temp es el nombre correcto del sensor de paquete; thermald no lo rechaza por schema. Resuelve el tipo en runtime contra los sensores que efectivamente descubrió, y como no había ninguno, la búsqueda volvió vacía y el bind falló. El mensaje dice "invalid type" pero quiere decir "no encontré un sensor de ese tipo en este sistema".

Resultado: el daemon arrancaba, intentaba bindear las zonas definidas en el XML, no encontraba el sensor, y se quedaba corriendo sin nada que monitorear. Vivo, ocupando memoria, sin función.

systemctl status te dice si systemd cree que el servicio está bien. No te dice si el servicio está haciendo lo que tiene que hacer. Para eso está journalctl.

Validación empírica

Antes de tomar la decisión final de desactivar thermald, corrí un baseline de 6 minutos midiendo Package temp y frecuencia, después un stress test de 6 minutos exactos midiendo lo mismo, y los comparé. Lo que vi fue lo que esperaba: con RAPL fijo en 12W y tuned manejando el resto en su profile balanced, las temperaturas se mantenían en una zona estable y la frecuencia se sostenía sin las caídas abruptas del clamshell sin tunear.

La pregunta que la medición respondía no era "¿thermald aporta?" — eso ya estaba contestado de antemano por los logs. Era la otra: "¿con solo RAPL+tuned alcanza para clamshell?". La respuesta fue sí, y eso permitió cerrar el setup con tranquilidad.

Estado final

tuned                 → active (profile balanced)
RAPL PL1=12W PL2=20W  → fijo vía rapl-limits.service
thermald              → disabled (no podía operar en este hardware)

Filosofía: un techo duro de potencia (RAPL), un orquestador de estado (tuned), nada reactivo encima. Tres piezas, ninguna pelea con la otra, todas debuggeables.

Una nota sobre cómo empecé

Originalmente armé el setup con tlp + RAPL, siguiendo una sugerencia de Claude (vía claude.ai) que asumía power-profiles-daemon como el daemon por defecto en Fedora. No contrasté la premisa. Cuando fui a verificarla, descubrí que desde Fedora 41 el default pasó a tuned (vía tuned-ppd), no PPD. Y para este escenario — clamshell estático, siempre AC, sin diferenciar AC/BAT, sin que tlp-rdw aporte nada — tlp no resolvía nada que tuned no resuelva. Migré. El post documenta el setup resultante, no el camino original.

La lección no es "no preguntarle a una IA". Es verificar la premisa que la IA está usando, no solo la recomendación que te entrega. Un asistente puede recomendar una herramienta correctamente condicional a un default que ya no es el actual; sin contrastar esa premisa, terminás con un setup más complicado del necesario.

Lo que me llevo

  • Un daemon puede estar "running" y no estar haciendo nada. La verdad está en los logs de aplicación, no en systemctl status.
  • Un unit file propio de pocas líneas a veces es la decisión correcta y que no requiere demasiado esfuerzo.
  • El consejo técnico de una IA puede ser internamente coherente y aún así basarse en premisas obsoletas (defaults, versiones, comandos). El costo de chequear la premisa antes de implementar es bajo; el costo de no hacerlo se paga cada vez que volvés a tocar el equipo.

3 Power Tips + Power Link I11

Las PTs de este issue muestran cómo, incluso en la nube (en este caso Azure), siguen aplicando conceptos clásicos de sistemas: distinguir entre ejecución y estado, entender la irreversibilidad de ciertos valores (como secrets) y operar en ausencia de un mapeo explícito entre componentes. Al final, el progreso en la Estrategia de Innovación Abierta y Código Abierto en un estado alemán.

Power Tip #1 El exit code es local, no de Azure

El comando volvió con 0 y la app sigue cayéndose.

  • az reporta éxito al recibir el ACK del control plane.
  • El recurso queda en Updating o Creating por minutos.
  • El siguiente paso del script lo encuentra "no listo" y falla raro.
az resource show --ids "$id" --query "properties.provisioningState" -o tsv

provisioningState es un testigo más confiable del control plane.


Power Tip #2 El valor del secret se entrega una sola vez

La alerta de vencimiento de un secret se "resolvió" pero la app igual se cae.

  • credential reset devolvió el password en stdout.
  • Lo perdiste antes de guardarlo en el vault.
  • Entra ID considera el secret válido. Nadie puede usarlo.
az ad app credential reset --append ... --query password -o tsv

Si se perdió el valor generado, no se puede recuperar. Como perder una clave privada SSH: podés crear otra, pero no reconstruir la anterior.


Power Tip #3 El mapeo no vive donde uno espera

Sabés que la app usa un secret de un KV. No sabés cuál.

  • App Registration no declara qué KV la consume.
  • Key Vault no mantiene, por defecto, una relación nativa con la App Registration que originó el secret.
  • El nombre del secret en KV es convención del equipo responsable, no metadato de Azure.
for kv in $(az keyvault list --query "[].name" -o tsv); do
  az keyvault secret list --vault-name "$kv" \
    --query "[?contains(name, 'mi-superpower-app')].{kv:'$kv',name:name}" -o tsv
done

Si para rotar un secret tenés que salir a buscarlo, no está definido en ningún lado qué secret usa esa app...

Este es un análisis del Open Source Observatory (OSOR) sobre la adopción de software Open Source en el estado alemán de Schleswig-Holstein. ¿Cómo les fue implementando LibreOffice, Thunderbird, and Linux?: From Early Adopter to Leader: Schleswig-Holstein's Open Source Evolution

3 Power Tips + Power Link I10

Las herramientas de monitoreo son fundamentales, pero una pobre interpretación de los resultados, pueden llevar a conclusiones incorrectas. Incluso usando chatbots de IA, si hacemos preguntas deficientes podemos terminar girando en círculos o tardar más tiempo en resolver un problema. Lo mejor es reducir el nivel de especulación con hipótesis técnica. A continuación vemos unos ejemplos bien sencillos.

Power Tip #1

No todas las métricas que parecen similares describen el mismo fenómeno.

  • Aplicación lenta.
  • Load 15.
  • CPU idle 50%.

Alguien descarta CPU porque “está libre”.

Pero:

  • Load describe tareas en ejecución o esperando.
  • CPU usage describe tiempo ocupado.
  • CPU pressure describe tiempo en que las tareas no pudieron progresar.

Son fenómenos distintos.

Antes de interpretar, mirá comportamiento:

vmstat 1 5
mpstat 1

Y si necesitás otra capa:

cat /proc/pressure/cpu

Es importante saber que “Load no es necesariamente consumo de CPU”, sin embargo, más importante es entender qué está describiendo cada señal.

Interpretarlas como si hablaran de lo mismo es especulación con números.

Power Tip #2

Tomar una métrica aislada de memoria en Linux no implica entender como funciona

free -h

Las columnas están ahí. Pero entender qué representa cada una es otra cosa.

  • used no significa “memoria en crisis”.
  • buff/cache no significa “desperdicio”.
  • available no significa “memoria libre inmediata”.

Antes de reaccionar ante un número alto, mirá dinámica:

vmstat 1 5

Linux administra memoria como recurso dinámico, no como espacio estático.

Leer columnas sin entender el modelo del kernel es especulación con formato tabular.

Power Tip #3

Reiniciar en muchas situaciones no hace otra cosa que restaurar un estado. No explica causas.

  • El servicio falla.
  • Se reinicia.
  • Funciona.

Eso no es diagnóstico.

Antes de repetir el gesto automático:

journalctl -u servicio-problematico -n 50

Si no entendemos por qué se degradó, volverá a pasar.

Reiniciar elimina el síntoma. No valida la hipótesis. Las herramientas no están equivocadas.

La diferencia está en entender qué fenómeno describe cada señal. Está claro que en el mundo real, muchas veces determinar la causa del problema lleva más tiempo que usar un trigger que reinice un servicio. Algo que pasa muchas veces apps legacy (la falta de especialistas o la triste realidad de falta del código fuente). Sin embargo, el problema es cuando se usa esta metodologìa como primera opción...

La IA terminará con las operaciones de IT. ¿En serio? Bueno, en realidad no, para sorpresa de tecno-optimistas o tecno-pesimistas, aquí hay un interesante análisis de Sebastián Martínez de SUSE sobre este tema: Why AI Still Cannot Run Your Linux Infrastructure (And What Must Change)

3 Power Tips + 1 Power Link I9

PSI (Pressure Stall Information) permite observar ese tipo de situaciones y, sobre todo, cambiar la forma en que interpretamos problemas de rendimiento y estabilidad en Linux. A partir de esa señal, el foco deja de estar en métricas globales y pasa a decisiones concretas: entender dónde ocurre el problema, a quién afecta y cómo intervenir sin arrastrar todo el sistema. Finalmente, el Power Link nos lleva a ver qué opina Gaël Duval, el creador de la distribución Mandrake, sobre uno de los hypes actuales.

Power Tip #1: ¿Cómo se ven afectadas mis aplicaciones por falta de recursos?

Existe a veces una creencia basado en pensamiento mágico: que el monitoreo depende solamente de la herramienta que se utilice. Pero más que la herramienta lo importante, es qué se mide y cómo interpretar esos resultados. Las métricas más conocidas: uso de cpu, load-average, uso de memoria, uso de I/O, etc. si bien son útiles pueden darnos en muchos casos un panorama parcial. Eso sucede porque están basados en los recursos, o en el scheduler.

Existen sin embargo desde hace varios años métricas que están más centradas en como las aplicaciones se ven impactadas por el uso de recursos. Con PSI (Pressure Stall Information) obtenemos un indicio del tiempo que las aplicaciones quedan detenidas porque el sistema no puede darles CPU, memoria o acceso a disco en el momento en que lo necesitan.

Esto está expuesto en el sistema de archivos:

Archivos de PSI

PSI distingue entre situaciones donde al menos una tarea queda bloqueada (some) y escenarios donde todas las tareas relevantes compiten por el mismo recurso (full). Valores elevados y sostenidos en este full caso suelen indicar un problema estructural: no de picos puntuales, sino de diseño, aislamiento o priorización de workloads.

Vale hacer aquí tres aclaraciones:

  1. Esta funcionalidad está presente a partir de la versión 4.20 del kernel (RHEL8 la trae como backport). En algunas distribuciones puede ser necesario habilitar PSI explícitamente en el arranque.
  2. Esta no es la métrica definitiva, sin embargo ignorarla es perder el cuadro completo de lo que sucede tanto en el sistema como nuestras aplicaciones.
  3. Versiones recientes del kernel incluyen también la medición de la presión sobre irq.

Power Tip #2: Las disrupciones se pueden atribuir a workloads específicos

Cuando el host empieza a perder capacidad de respuesta, no siempre el problema es global. Es común que el sistema siga siendo usable mientras algunos servicios o aplicaciones comienzan a degradarse, simplemente porque no acceden a los recursos cuando los necesitan. Esa degradación suele manifestarse como lentitud, mayor latencia o pérdida de capacidad de interacción.

En un Linux moderno, el kernel organiza los procesos bajo dominios de control definidos por cgroups. Esto permite observar de forma localizada qué tareas empiezan a quedarse esperando recursos, en lugar de razonar únicamente a partir de métricas agregadas del host.

En sistemas basados en systemd, ese dominio no es abstracto: se corresponde con services, slices o scopes. El mismo síntoma que aparece a nivel global puede observarse dentro de estos límites, lo que permite atribuir dónde se generan las esperas, incluso cuando el resto del sistema continúa funcionando con normalidad.

Pensar a nivel host diluye el diagnóstico. En cambio poner el foco en en services, scopes y slices permite relacionar directamente los síntomas percibidos —lentitud, tiempos de respuesta, degradación de rendimiento— con un grupo de procesos concreto.

PSI per cgroup

Es decir, uno puede decir que el host muestra la presión acumulada, pero los cgroups permiten entender qué se queda atascado y frente qué recurso.

Power Tip #3: Abordar el problema de manera localizada

Cuando un servicio o aplicación empieza a tener tiempos muertos o a interferir con el resto, tenemos varias maneras de abordar el problema. Y no estamos hablando de las típicas acciones: reiniciarlo, moverlo o rediseñarlo.

Podemos tomar medidas más estructurales, por ejemplo, crear un slice de systemd, establecer límites y luego confinar esos servicios o procesos en esos slices.

Sin embargo, antes de intervenir de manera prematura, es conveniente tener en cuenta que Linux, en general ya tiene una agrupación de slices, scopes y servicios bastante razonable. En este punto entonces, ya sabemos que ese servicio problemático ya corre dentro de un perímetro definido.

En lugar de crear un nuevo slice, que tiene un carácter más permanente, podemos adoptar una solución más sencilla y gradualista. En systemd, los parámetros que gobiernan cómo compite por recursos no están fijos en scopes o servicios.

Un vistazo rápido alcanza para verlo (los siguientes son ejemplos para entender la idea):

systemctl show -p ControlGroup,CPUWeight,MemoryMax,IOWeight procesos-ruidosos.scope

Y tomar cartas en el asunto:

systemctl set-property procesos-ruidosos.scope 

Ese tipo de intervención es local, reversible y no convierte un incidente puntual en un cambio estructural.

Intervenir sobre una unidad existente permite corregir interferencias concretas sin rediseñar el sistema ni propagar el problema.

Fuentes y más recursos

Qué son los cgroups y para qué sirven

Gaël Duval, creador de Mandrake, Murena y e/OS reflexiona sobre el impacto de la AI en el software open source: Why AI won’t “Kill Open Source”

Plan Táctico y Estratégico de la Memoria en Linux

La mitología en torno a la memoria en Linux ha producido una serie de relatos:

  • En un extremo: Linux puede funcionar con muy poca memoria RAM.
  • Del otro lado: Linux consume mucha memoria.
  • Y que una partición SWAP debe tener entre 1 a 2 veces la memoria RAM.

Como vemos algunas historias son más recientes, otros más antiguas, pueden ser parcialmente ciertas y hasta contradictorias entre sí.

Este artículo tiene como propósitos:

  • Explicar de manera sencilla el funcionamiento de la memoria en Linux, desmitificando también algunos conceptos.
  • Enumerar y describir tácticas para que el uso de la memoria proporcione la mejor usabilidad y experiencia del usuario.
  • Ofrecer alternativas para que cada uno elija la mejor opción de acuerdo a sus necesidades.

Definiciones

Vamos a repasar algunos conceptos básicos que de manera más o menos frecuente usamos, usaremos metáforas en el camino. Ninguna metáfora es perfecta, sin embargo ellas nos ayudan a entender la realidad.

Memoria Virtual

La memoria virtual es el mecanismo por el cual cada proceso recibe un espacio de direcciones propio, independiente y protegido. El hardware traduce direcciones virtuales a físicas según tablas de páginas. Esto permite aislamiento, sobreasignación, mapeos de archivos y demanda dinámica de páginas, más allá de la cantidad de RAM disponible.

Linux trata de usar la mayor cantidad de memoria posible, para poder ejecutar las aplicaciones y acceder a los archivos de la manera más rápida posible. De manera que si la memoria libre es baja no es necesariamente un indicativo de un problema.

Page

Una página es la unidad mínima de memoria, de tamaño fijo (típicamente 4 KB), que el kernel y el hardware de gestión de memoria del procesador utilizan para organizar el espacio de direcciones y realizar el mapeo entre direcciones virtuales y físicas.

Page table

Es una estructura de datos jerárquica administrada por el kernel y usada por el hardware para traducir direcciones virtuales a direcciones físicas, con información de permisos y estado de cada página.

Page Fault

Un page fault ocurre cuando un proceso accede a una dirección válida de su espacio de memoria pero la página correspondiente no está preparada para ese acceso. Puede deberse a que la página aún no fue cargada, a que debe asignarse, o a que debe traerse desde disco. Si la página no está en RAM, el kernel debe cargarla, lo que puede ser lento; si ya estaba en RAM, el costo es menor.

Page cache

Es la caché de páginas de archivos gestionada por el kernel, usada para acelerar lecturas y escrituras almacenando en RAM los datos y metadatos de archivos y directorios, reduciendo accesos al disco.

Page cache

Tipos de memoria

File Memory

Es la memoria relacionada con el Page Cache.

Anonymous Memory

Es la memoria que un proceso usa y que no está respaldada por un archivo: heap (asignada dinámicamente), stack (llamadas a funciones y almacenamiento de variables locales), COW (parte de la memoria cuando se crea un proceso hijo) y mapeos con MAP_ANONYMOUS. Su único respaldo posible es la swap. La memoria anónima se crea y utiliza en RAM. Si el kernel necesita expulsarla de RAM, su único destino posible es la swap, porque no tiene un archivo desde el cual reconstruirse. Pero su existencia normal no depende de la swap.

Memory Pressure

Memory pressure es estado en el que las páginas libres caen por debajo de umbrales críticos, obligando al kernel a iniciar mecanismos de liberación de memoria.

En términos prácticos cuando la presión es alta pueden surgir ciertos síntomas:

  • Sitios web / servidores HTTP: La latencia aumenta porque los workers deben esperar a que el kernel libere páginas. Además, puede haber más CPU gastada en recuperar memoria, lo que degrada aún más los tiempos de respuesta.

  • Sistemas de escritorio: El sistema pierde fluidez porque el scheduler empieza a verse afectado por stalls debidos a las operaciones para liberar memoria y, si hay swap, el sistema puede entrar en swap thrashing. Esto produce lag del mouse, ventanas que tardan en responder, escritorios congelados por segundos, etc.

  • Acceso remoto (SSH, RDP, VNC): Al haber presión, las operaciones de usuario-espacio tardan más en ejecutarse, y los daemons pueden quedar brevemente estancados esperando memoria. Esto causa retrasos notables en la interacción remota.

¿Y qué sucede si la presión de memoria puede llegar a ser tan alta que el kernel ya no logra conseguir memoria ni siquiera después de intentar liberarla?

Thrashing

El thrashing ocurre cuando la memoria RAM no alcanza para mantener las páginas que los procesos usan todo el tiempo. El kernel empieza a sacar páginas de memoria para hacer lugar a otras nuevas, pero enseguida vuelve a necesitarlas. Esto genera un bucle de fallos de página y de recarga constante desde el disco.

  • Con swap: las páginas anónimas van y vienen entre RAM y swap, lo que dispara el uso de disco y vuelve el sistema extremadamente lento.
  • Sin swap: las páginas anónimas no tienen adónde ir y el kernel termina activando el OOM killer.
  • Incluso sin swap: puede haber thrashing si las páginas vienen de archivos (page cache), ya que el kernel debe recargarlas una y otra vez.

OOM (Out-Of-Memory) 💀 🔥

Out-Of-Memory es una situación en la cual el kernel agotó todos los mecanismos para liberar memoria:

  • Liberar páginas del caché.
  • Sacar páginas anónimas de swap.
  • Compactar memoria.
  • Liberar memoria mediante kswapd.
  • Aplicación de políticas de cgroups (muy común al usar contenedores).

OOMKiller

El OOM-killer es el mecanismo que usa el kernel cuando ya no puede asignar más memoria, incluso después de intentar liberar todas las páginas que es posible descartar o mover fuera de la RAM. En esa situación crítica, el kernel calcula un puntaje para cada proceso (oom_score) y finaliza al que resulte más conveniente para recuperar memoria rápidamente y permitir que el sistema siga funcionando. Este comportamiento puede influenciarse ajustando oom_score_adj, que hace que un proceso sea más o menos propenso a ser elegido.

oom_score

Como ya se mencionó a cada proceso se le asigna un puntaje de acuerdo a distintos factores, cuanto más alto es, más susceptible es a ser terminado por OOMKiller.

OOM Scores por proceso en /proc

Y también, como dijimos mediante oom_score_adj podemos influir en el score de un proceso:

Incidencia en oom_score mediante oom_score_adj

En versiones más recientes de las distribuciones existe el comando choom que permite ver y/o ajustar dicho valor.

choom

Swap

Los usuarios ocasionales de Linux y aun muchos sysadmins tienen una idea negativa sobre "la swap". Simplificaciones extremas y conceptos anticuados la han convertido en le gran villana de la historia del sistema operativo.

Si comparamos a la memoria con un escritorio, sin swap podría lucir así:

Prescindir de swap no es una opción sana.

Photo by Ashim D’Silva on Unsplash

Así que primero vamos a decir lo que no es:

  • No es la memoria virtual sino que forma parte de la técnica que realiza el sistema operativo para administrar la memoria.
  • No es un espacio de reserva ni un último recurso. Es un espacio complementario sirve para liberar RAM.
  • No funciona como último recurso, pero el sistema operativo puede mover hacia la swap las páginas de memoria no usadas recientemente.
  • No es algo que el sistema operativo pueda alegremente prescindir aun cuando la cantidad de memoria RAM física sea grande. Quienes parecen haber inventado la pólvora, nos cuentan que es posible técnicamente que Linux funcione sin swap. Si bien es cierto, al carecer de swap, el kernel no tiene manera de mover páginas de memoria anónimas hacia el área de swap y liberar así RAM para procesos que la necesitan urgentemente.

Nuestro escritorio con swap:

Analogía de Swap

Photo by Alexandru Acea (edited by me) on Unsplash

¿Los cajones de un escritorio los usamos cuando lo tenemos abarrotado de cosas? No, los usamos para guardar cosas que no son de alta prioridad. Aunque es cierto, si luego queremos usar esa tijera o aquel destornillador en algún momento requerirá un poco más de trabajo, tendremos que abrir el cajón, buscarlo, extraerlo, etc.

Ah, y la swap también sirve para hibernar, aunque honestamente no se cuanta gente mantiene esa práctica.


Tuning

Ahora veremos diferentes tácticas que podemos usar para optimizar el uso de la memoria.

cgroupv2

cgroup es un mecanismo para organizar los procesos de manera jerárquica y distribuir los recursos del sistema a lo largo de la jerarquía en una manera controlada y configurada.

Un cgroup se compone de un núcleo que es responsable primariamente en organizar de manera jerárquica los procesos y controladores que comúnmente distribuyen un tipo específico de recurso del sistema a lo largo de la jerarquía.

En la versión 2 de cgroup un proceso no puede pertenecer a diferentes grupos para diferentes controladores. Si el proceso se uno al grupo alfa, todos los controladores para alfa tomarán control de ese proceso.

ps mostrando cgroup

Supongamos que los procesos de un cgroup (y todos los grupos hijos) usan poca memoria, podríamos decirle al kernel que libere memoria de otros cgroups. Esto es precisamente lo que hace el parámetro memory.low.

el parámetro memory.low

Otro parámetro interesante para monitorear es memory.pressure, la primera línea tiene el tiempo físico de una o más tareas demoradas debido a la falta de memoria. La segunda sería lo mismo pero para todas las tareas del grupo, full es lo mismo pero para todas las tareas del grupo, Entonces si miramos el archivo /sys/fs/cgroup/user.slice/memory.pressure:

some avg10=0.00 avg60=0.13 avg300=0.12 total=1690238
full avg10=0.00 avg60=0.10 avg300=0.09 total=1394199

Significa que, dentro del grupo de control user.slice, en los últimos 10 segundos no hubo tareas afectadas por presión de memoria. Sin embargo, en el último minuto las tareas estuvieron bloqueadas un 0,13% del tiempo y un 0,12% en los últimos cinco minutos. El valor total indica que estas situaciones acumulan aproximadamente 1,7 segundos de espera. La segunda línea muestra las mismas métricas, pero aplicadas a los casos en que todas las tareas del grupo estuvieron simultáneamente afectadas por presión de memoria (full en lugar de some).

Es decir:

  • some → indica si un retraso afectó al menos una tarea.

  • full → indica si el retraso afectó a todo el cgroup simultáneamente.

zram

zram es por así decirlo, una manera cool de usar swap gracias a un módulo del kernel.
zram, swap pero cool

Photo by chuttersnap on Unsplash

En lugar de gastar espacio en un disco (sea rígido o sólido) usamos dispositivos de bloque en la propia RAM. Los bloques swapeados se guardan comprimidos. Esto discos virtuales son rápidos y ahorran memoria.

Una de las pocas desventajas que tiene esta metodología es la incapacidad para poder hibernar el sistema operativo, al no estar presente la partición en un almacenamiento de tipo persistente.

zram

Sistemas de archivos

El journal de ext4 puede ser lento, xfs puede ser una mejor alternativa o mejor aun btrfs.

EarlyOOM

El oom-killer del kernel solamente se dispara en situaciones extremas y le puede llevar mucho tiempo hasta que puede enviar SIGKILL a los procesos que sean necesarios para poder liberar memoria. Durante ese tiempo probablemente el usuario no pueda interactuar con el sistema operativo.

EarlyOOM trabaja en espacio de usuario y por lo tanto se puede anticipar y ser mucho más rápido.

El comportamiento predeterminado en Fedora es que si hay menos del 10% de RAM y/o SWAP libre, earlyoom envía una señal de terminación a todos los procesos con oom_score más alto. Si la RAM como SWAP libre bajan por debajo del 5%, earlyroom enviará una señal para matar todos los procesos con oom_score más elevado.

La idea es recuperar la usabilidad (especialmente en un entorno de escritorio) lo antes posible.

El problema es que EarlyOOM no soporta al momento la medición de la memory pressure como indicativo para tomar decisiones.

nohang

Este servicio es mucho más configurable y aporta una mejor solución que EarlyOOM.

Algunas funcionalidades son:

  • Se puede elegir la acción que realizará en una situación OOM.
  • Ofrece varios criterios para elegir los procesos a finalizar.
  • Soporta zram
  • Puede usar memory pressure para tomar una acción.
  • El archivo de configuración es medianamente sencillo

Sin embargo, este proyecto en la actualidad tiene poca actividad comparado por ejemplo con EarlyOOM

Repos: nohang vs EarlyOOM en Github

zswap

Con zswap no reemplazamos el espacio swap en el disco sino que usamos un caché comprimido en la RAM. Este método ahorra I/O, obteniendo entonces mejor rendimiento y alargando la vida útil de discos flash o sólidos. La única desventaja es usar algo de tiempo del procesador para realizar la compresión.

zswap

Photo by Pineapple Supply Co. on Unsplash

Mediante el caché se logra una diferenciación entre páginas más usadas (zswap) y menos usadas (swap).

systemd-oomd

El servicio systemd-oomd es un proyecto en el que comenzó en Facebook para integrarlo con systemd. En un principio estaba pensado para manejo de memoria a gran escala, y bastante más complejo de configurar. Sin embargo ha sido adoptado desde Fedora 34 reemplazando a EarlyOOM. En las distribuciones que usan versiones recientes de systemd, está disponible, aunque no todas lo habilitan de manera predeterminada.

systemd-oomd

Resumen

  • Swap no es la villana de la película
  • Si existe la opción de migrar a otros sistema de archivos aunque con características un tanto experimental, elegir btrfs. Una opción más moderada es xfs.
  • El tuning de cgroupv2 puede traer grandes beneficios, no obstante existen proyectos y distribuciones que no lo usan.
  • EarlyOOM es una solución rápida y aplicable a una amplia gama de sistemas Linux, aunque no siempre es la más exacta ni más elegante.
  • El servicio nohang (o no hang-desktop) es una opción más madura aunque algo más compleja que EarlyOOM, pero que sin embargo ha caído en cierta inactividad.
  • El servicio systemd-oomd inicialmente incorporado por Facebook es seguramente la opción más adecuada para escenarios más complejos y de manejo de memoria a gran escala. También es utilizado actualmente en sistemas de escritorio.
  • Sin embargo, muchas distribuciones o sabores de distribuciones prefieren usar el mecanismo clásico de OOMKiller.
  • A veces se sugiere el ajuste de parámetros del kernel mediante sysctl.
  • Si se desea ahorrar espacio en disco se puede reemplazar la swap por zram, sacrificando la opción de hibernar el sistema.
  • La opción zswap es más sofisticada, aunque dependemos del uso de swap en disco.

Photo by sk on Unsplash

Fuentes consultadas

3 Power Tips + 1 Power Link I8

Resumen: En esta entrega, un ejemplo del poder que tienen la subshells de bash, como solucionar problemas de SELinux con podman, y el uso de la herramienta yq para procesar archivos yaml.

Nota: En los ejemplos de comandos el prompt del usuario no privilegiado es "$", mientras que el del superusuario es "#"

Power Tip #1: Crear entornos de bash efímeros usando subshells

Una subshell es una copia del proceso de la shell actual. Una manera de crear una subshell es usando () y sirve: para personalizar el entorno de bash de manera reversible. Por ejemplo, podemos cambiar de directorio y la variable de entorno C (POSIX) para que muestre los mensajes de salida y de error en inglés:

$ echo "$LANG"
es_AR.UTF-8
$ pwd
/home/sergio

Ahora creamos una subshell:

$ (LANG=C; echo "La variable LANG cambia a  $LANG"; cd /algun_dir || cd /tmp ; echo "el directorio actual es $PWD";  myscript.sh)
La variable LANG cambia a  C
-bash: cd: /algun_dir: No such file or directory
el directorio actual es /tmp
-bash: myscript.sh: command not found

Al terminar la subshell, volvemos a la shell madre y tanto el directorio de trabajo como la variable LANG, vuelven a sus valores predeterminados:

$ pwd
/home/sergio
$ echo "$LANG"
es_AR.UTF-8

Power Tip #2: Identificar y solucionar problemas de SELinux al usar volúmenes.

Supongamos que necesitamos probar una configuración en un contenedor:

$ podman run  --name mynginx  -d -v $HOME/sandbox/nginx.conf:/etc/nginx/nginx.conf --pull=never docker.io/library/nginx@sha256:553f64aecdc31b5bf944521731cd70e35da4faed96b2b7548a3d8e2598c52a42

Ahora bien: ¿Qué sucede si el contenedor en realidad, falla al arrancar como lo muestran los logs?:

$ podman logs mynginx 
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/11/24 20:09:21 [emerg] 1#1: open() "/etc/nginx/nginx.conf" failed (13: Permission denied)
nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (13: Permission denied)

Si estás tentado a desactivar SELinux, te recomiendo enfáticamente que leas ¿SELinux? Nah, dejalo en Disabled. Lo que ocurre es que no hay ninguna regla que permita que el contenedor acceda al contexto del archivo nginx.conf del host.

Contexto del archivo:

$ ls -Z  $HOME/sandbox/nginx.conf
unconfined_u:object_r:user_home_t:s0 /home/sergio/sandbox/nginx.conf

Este problema se soluciona fácilmente:

podman stop mynginx
podman rm mynginx

Y ahora el contenedor arrancará sin inconvenientes:

podman ps
CONTAINER ID  IMAGE                                                                                            COMMAND               CREATED         STATUS         PORTS       NAMES
a29c08eda1b1  docker.io/library/nginx@sha256:553f64aecdc31b5bf944521731cd70e35da4faed96b2b7548a3d8e2598c52a42  nginx -g daemon o...  12 seconds ago  Up 12 seconds              mynginx
podman logs mynginx 
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/11/24 21:16:10 [notice] 1#1: using the "epoll" event method
2025/11/24 21:16:10 [notice] 1#1: nginx/1.29.3
2025/11/24 21:16:10y [notice] 1#1: built by gcc 14.2.0 (Debian 14.2.0-19) 
2025/11/24 21:16:10 [notice] 1#1: OS: Linux 4.18.0-553.84.1.el8_10.x86_64
2025/11/24 21:16:10 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 262144:262144
2025/11/24 21:16:10 [notice] 1#1: start worker processes
2025/11/24 21:16:10 [notice] 1#1: start worker process 28
2025/11/24 21:16:10 [notice] 1#1: start worker process 29

Ahora bien, la opción :Z no es mágica, para tener una aproximación a lo que hace veamos el contexto del archivo $HOME/sandbox/nginx.conf:

$ ls -Z  $HOME/sandbox/nginx.conf
system_u:object_r:container_file_t:s0:c369,c839 /home/sergio/sandbox/nginx.conf
Campo Antes Después Comentario
Usuario (SELinux) unconfined_u system_u Pasa de ser un usuario sin restricciones a un usuario gestionado por el sistema
Rol (SELinux) object_r object_r Se mantiene sin cambios
Asignación basada en tipos user_home_t container_file_t Cambia a un tipo de asignación en la cual los contenedores pueden escribir
Rango (SELinux) s0 s0:c369,c839 Este cambio implica que solamente podrá utilizarlo de manera exclusiva un único contenedor

Power Tip #3: Filtrar un archivo de template en formato yaml de Zabbix sin instalar absolutamente nada

Solamente necesitamos podman, en el siguiente ejemplo estamos filtrando todos los triggers estáticos con nivel de severidad HIGH o DISASTER:

podman run --rm -v "${PWD}":/workdir:Z mikefarah/yq  '.zabbix_export.templates[].items[].triggers[] | select(.priority == "HIGH" or .priority == "DISASTER")| {"Trigger": .name}' template_db_mssql_agent2.yaml
Trigger: 'MSSQL: Percentage of the buffer cache efficiency is low'
Trigger: 'MSSQL: Page life expectancy is low'
Trigger: 'MSSQL: Percentage of work tables available from the work table cache is low'
Trigger: 'MSSQL: Service is unavailable'

Fuentes y más recursos

El enlace de esta edición apunta a una entrevista a Linux Torvalds que gira en torno a la evolución del hardware, la IA y su impacto en el Linux y el desarrollo del kernel: Linus Torvalds — Talks about AI Hype, GPU Power, and Linux’s Future . En la actualidad cuestiones como la programación guiada por la intuición con la ayuda de chatbots debates importantes, no solamente en Linux sino en el ámbito IT en general.

3 Power Tips + 1 Power Link I7

Resumen: Tips para bash, KDE Plasma y el uso del shell para cambiar entre ambientes de desarrollo. Además, en el Power Link, un artículo de Aaron P. MacSween, en el que discute el uso de AI scraping de sitios web usados de manera no consensuada para entrenar Modelos Extensos de Lenguaje (LLMs).

Power Tip #1: Agrupación de comandos en bash

El shell bash permite agrupar comandos, una funcionalidad tan sencilla como potente, por ejemplo:

{ dnf -y upgrade && systemctl restart httpd mariadb php-fpm ; } || echo "The final status is unsuccessful 🙁"

De esta manera si algunos de los dos comandos falla, mostrará un mensaje de error unificado.

Power Tip #2: Uso de cuadros de diálogos nativos de KDE Plasma en Firefox

Para hacerlo hay que abrir la url about:config y luego setear la preferencia widget.use-xdg-desktop-portal.file-picker en 1.

Set preferences on Firefox

Esto se basa en el uso de XDG Portals que es un elemento de la arquitectura de entornos gráficos. Ellos proporcionan sandboxing, funcionalidad en entornos Wayland y lo que nos interesa en este caso: coherencia en la interfaz del usuario.

Antes 🤐 Después 😎️
Set preferences on Firefox Before and After of setting file picker

Ah... usar la variable de entorno MOZ_USE_XDG_PORTAL no es la manera recomendada, más allá de lo que diga cierto chatbot...

Power Tip #3: [Flask + Linux]: Entornos Seguros usando solo la Shell

Problema Común

Ejecutar una app Flask con DEBUG=True en producción expone trazas de errores, rutas internas y detalles del stack que pueden ser explotados. Un sysadmin o devops con experiencia sabe que una práctica profesional es externalizar la configuración y usar el sistema operativo como interruptor de entorno.

Solución Basada en Linux + Flask

Usá la variable de entorno de Linux FLASK_ENV para alternar entre una configuración segura de Producción (config_prod.py) y una configuración orientada a Desarrollo (config_dev.py).

Archivo Ajuste Enfoque
config_dev.py DEBUG = True Depuración y agilidad.
config_prod.py DEBUG = False (más SECRET_KEY) Seguridad y Gestión de Secretos.

En config_prod.py, además de DEBUG = False, la clave secreta se carga desde el entorno con SECRET_KEY = os.environ.get('PROD_KEY') — así nunca queda hardcodeada en el repo.

Fragmento de app.py

import os
from flask import Flask
# Se importan las clases para un código robusto:
from config_dev import DevelopmentConfig 
from config_prod import ProductionConfig

app = Flask(__name__)

if os.environ.get('FLASK_ENV') == 'production':
    # Entorno PROD: Carga DEBUG=False
    app.config.from_object(ProductionConfig) 
else:
    # Entorno DEV: Carga DEBUG=True por defecto
    app.config.from_object(DevelopmentConfig) 

Control desde la Terminal

+--------------------------+----------------------------------------------------------------------------------+---------------------------------------------------+ | Entorno | Comando | Resultado | +==========================+==================================================================================+===================================================+ | Producción | export FLASK_ENV=production | Debug mode: off | | | | | | | python app.py | (modo seguro) | +--------------------------+----------------------------------------------------------------------------------+---------------------------------------------------+ | Desarrollo | unset FLASK_ENV | Debug mode: on | | | | | | | python app.py | (vuelve a depuración) | +--------------------------+----------------------------------------------------------------------------------+---------------------------------------------------+

✅ Conclusión:

Este patrón convierte tu shell de Linux en un switch seguro de entorno para tu app Flask.

Preguntas de repaso

  1. Seguridad y DevOps (Power Tip #3): ¿Cuál es el riesgo principal de ejecutar una aplicación Flask con DEBUG=True en producción, y cómo permite la variable de entorno de Linux FLASK_ENV que el administrador (sysadmin o devops con experiencia) cambie de manera segura a la configuración de producción (config_prod.py) usando solo un comando en la shell?
  2. Eficiencia en Bash (Power Tip #1): En el ejemplo de agrupación de comandos, ¿cuál es el propósito de encerrar las sentencias de actualización (dnf -y upgrade) y de reinicio de servicios (systemctl restart ...) entre llaves {}, y qué rol juega el operador lógico || en caso de que alguna de estas operaciones falle?
  3. Experiencia de Usuario (Power Tip #2): Si un usuario de KDE Plasma desea que Firefox utilice los cuadros de diálogo nativos del escritorio en lugar de los predeterminados del navegador, ¿qué URL debe abrir y a qué valor debe establecer la preferencia widget.use-xdg-desktop-portal.file-picker?

AI scrapers request commented scripts. ¿Hasta qué punto una actividad es considerada benigina cuando se entrenan LLMs? Y muchas cuestiones para pensar...

3 Power Tips + 1 Power Link I6

Resumen: Tips para KDE Plasma, manejo de directorios temporales e información detallada sobre podman. Además, un link a un post en el cual un líder de ingenería recomienda pasar de Docker a Podman.

Power Tip #1 Reiniciar KDE Plasma sin cerrar sesión

kquitapp6 plasmashell ; plasmashell --replace >/tmp/plasma.log 2>&1 & disown -h

Power Tip #2 Crea un sandbox con limpieza automática usando tmpfiles.d

Podemos tener un directorio al cual usamos como sandbox, por ejemplo para:

  • Descarga de archivos
  • Volcar Logs y dumps temporarios
  • Scripts, código... y cualquier cosa que queremos probar.... por un tiempo.

Pero la idea es que no queremos que esos archivos se almacenen de manera indefinida... entonces:

cat << EOF >  ~/.config/user-tmpfiles.d/user-sandbox.conf 
> d %h/sandbox - - - 7d
> d %h/sandbox/ephemeral - - - 10m
> EOF

Una idea tentadora es usar un directorio como /tmp. Pero no es buena práctica usar ese directorio como sandbox. Luego de crear ese archivo, hay que ejecutar: systemd-tmpfiles --create --user que creará los directorios en las rutas especificadas. El servicio systemd-tmpfiles-clean eliminará periódicamente los archivos más viejos de 7 días del directorio ~/sandbox. El contenido de ~/sandbox/ephermeral en cambio, no se borrará automáticamente excepto que creemos una unit de tipo timer. Sin embargo se puede hacer manualmente mediante systemd-tmpfiles --clean --user. Como siempre entender por qué hacés lo que hacés es fundamental, los manpages son siempre aliados a tener en cuenta aun en pleno hype de la AI.

A propósito... el comando en el PowerTip #1 podría haber usado el directorio del sandbox en lugar de /tmp....

Power Tip #3 Revisar espacios y objetos de manera detallada en Podman

podman system df -v

De esta manera obtenemos información muy útil sobre las imágenes, contenedores y volúmenes que tenemos.

Espacio ocupado por podman

Entrevista a Guido van Rossum, creador de Python, en la cual trata entre temas sobre AI

3 Power Tips + 1 Power Link I5

Resumen: Tips para containers, uso de digests y análisis de seguridad de imágenes y obtener información de imágenes remotas. Además, un link a un post en el cual un líder de ingenería recomienda pasar de Docker a Podman.

Power Tip #1 Etiquetar las imágenes con digest en producción

❌ Mala práctica, apuntar a un tag:

podman run -d --name pass_git localhost/passteiner-ubi9:1

Esta es una mala práctica porque:

  • Es mutable, puede apuntar a otro build
  • También es un error confiar ciegamente usar latest, hoy puede apuntar a una versión, mañana a otra
  • Dificulta el debugging al no saber exactamente qué versión usás

✅ Buena práctica:

podman run -d --name pass-git --pull=never \
  localhost/passteiner-ubi9@sha256:3ca1e63acb24d88fde5e86eb1f476ba69eb740cd43590c37f3a964b5a19f001

Ventajas:

  • Totalmente reproducible y auditable.

  • Evitás actualizaciones sorpresa.

  • Ideal para ambientes de producción, CI/CD, y entornos donde la confiabilidad importa.

Power Tip #2 Examinar vulnerabilidades en imágenes de contenedores

La abstración con que nos proporcionan los contenedores, tal vez nos den una falsa sensación de seguridad, sin embargo, con trivy se pueden escanear en una imagen vulnerabilades por ejemplo:

trivy image docker.io/bitnami/wordpress

Debajo podemos ver un resumen:

Resumen del reporte realizado por trivy

Power Tip #3 Examinar imágenes de contenedores sin necesidad "pullearlas"

skopeo inspect docker://docker.io/ollama/ollama \
  | jq '{Digest: .Digest, Labels: .Labels, UltimaCapa: .Layers[-1]}'
{
  "Digest": "sha256:a5409cb903d30f9cd67e9f430dd336ddc9274e16fd78f75b675c42065991b4fd",
  "Labels": {
    "org.opencontainers.image.ref.name": "ubuntu",
    "org.opencontainers.image.version": "24.04"
  },
  "UltimaCapa": "sha256:45fafbfc0e267e3b61858ae5cb28ff739d901d85e1b60ee62db5dad64ae7c0d5"
}

De esta manera obtenemos:

  • El Digest de la imagen (identificador único inmutable).
  • En qué distro base y versión se construyó la imagen (ubuntu:24.04).
  • El Digest de la última capa, que representa el último cambio al sistema de archivos durante el proceso de build.

El digest de la última capa representa el último cambio al sistema de archivos en el proceso de construcción de la imagen.

Why I Ditched Docker for Podman And You Should Too

Gestión de Contraseñas Usando Contenedores Podman (Update 2025)

En 2023, publiqué un post sobre cómo usar pass con un contenedor Git administrado por Podman. Esa guía fue muy útil como demo inicial, pero desde entonces cambiaron algunas cosas: nuevas versiones de Podman, la aparición de Quadlet, la deprecación de podman generate systemd, y la conveniencia de usar imágenes más estables.

Este post es una actualización 2025 del artículo original, incorporando:

  • Imagen base UBI9 minimal (más liviana y con soporte extendido). Esta imagen, desarrollada por Red Hat, es de tamaño relativamente pequeño, usa una versión reduccida de dnf, llamada microdnf con soporte de módulos y usa software de repositorios - también - de Red Hat.
  • Rootless limpio con UserNS=keep-id.
  • Quadlet para integrar con systemd --user.
  • Volúmenes persistentes para no perder datos ni claves al actualizar la imagen.
  • Endurecimiento SSH (solo claves públicas, nada de contraseñas).

1. Construcción de la imagen

Dockerfile (Dockerfile):

FROM registry.access.redhat.com/ubi9/ubi-minimal

RUN microdnf -y install openssh-server git shadow-utils \
    && microdnf clean all \
    && useradd -ms /bin/bash git \
    && chsh -s /usr/bin/git-shell git

EXPOSE 2222
CMD ["/usr/sbin/sshd", "-D", "-e"]

Construcción:

podman build -t localhost/passteiner-ubi9:1 .

2. Crear volúmenes persistentes

podman volume create git-home
podman volume create ssh-etc

VHOME=$(podman volume inspect -f '{{.Mountpoint}}' git-home)
mkdir -p "$VHOME/.ssh" "$VHOME/.password-store"
cp ~/.ssh/id_ed25519.pub "$VHOME/.ssh/authorized_keys"
chmod 700 "$VHOME/.ssh"
chmod 600 "$VHOME/.ssh/authorized_keys"

Repositorio bare:

podman unshare chown -R 1000:1000 "$VHOME"
podman run --rm -v git-home:/home/git localhost/passteiner-ubi9:1 \
  git init --bare /home/git/.password-store

3. Quadlet (systemd integration)

Archivo ~/.config/containers/systemd/pass_git.container:

[Unit]
Description=pass git over SSH (podman quadlet)
Wants=network-online.target
After=network-online.target

[Container]
Image=containers-storage:localhost/passteiner-ubi9:1
Pull=never

ContainerName=pass_git
Network=pasta
PublishPort=60003:2222

Volume=git-home:/home/git:Z,U
Volume=ssh-etc:/etc/ssh:Z

UserNS=keep-id
LogDriver=journald
Exec=/usr/sbin/sshd -D -e -p 2222 -o PidFile=/tmp/sshd.pid

[Service]
Restart=on-failure

[Install]
WantedBy=default.target

Activación:

systemctl --user daemon-reload
systemctl --user enable --now pass_git.service

4. Probar conexión SSH

ssh -p 60003 git@<IP_DEL_HOST> \
  -o PreferredAuthentications=publickey \
  -o PasswordAuthentication=no \
  'git --version && echo OK'

Debe autenticar con tu clave pública y responder git version ....


5. Probar pass con git

Inicializar pass en tu host y configurar el remoto:

export PASSWORD_STORE_DIR=~/.local/share/pass
pass init "tu_clave_gpg"
cd "$PASSWORD_STORE_DIR"
git remote add origin ssh://git@<IP_DEL_HOST>:60003/home/git/.password-store
git push origin master

Y luego:

pass git push
pass git pull

Diferencias clave respecto al artículo original

  • Imagen: antes Fedora, ahora UBI9 minimal (más estable, soporte hasta 2032).
  • Systemd: antes podman generate systemd, ahora Quadlet (futuro estándar, más limpio).
  • Rootless: antes root dentro del contenedor, ahora UserNS=keep-id.
  • Volúmenes: ahora explícitos (git-home, ssh-etc), evitando pérdidas de datos.
  • Seguridad SSH: solo claves, sin password, PidFile movido a /tmp.

Conclusión

Este enfoque actualizado:

  • Evita perder el repositorio y las claves al recrear el contenedor.
  • Asegura compatibilidad a futuro con Podman + systemd.
  • Se apoya en una base más segura (UBI9 minimal).

👉 Si ya usás el setup original, podés migrar en pocas horas y quedarte tranquilo de que tu gestor de contraseñas pass seguirá funcionando a largo plazo.