Un gadget ARM de 10 dólares con solo 512 MB de RAM muestra en la práctica los límites y las posibilidades del self-hosting con poca ram. El desarrollador suizo Bosscher instaló Debian, systemd y Podman sobre un OpenStick y terminó corriendo seis servicios reales con los 450 MB que le quedan libres tras el sistema.
Ese presupuesto tan ajustado sirve como caso de estudio para cualquier placa ARM barata: una Raspberry Pi Zero, un router reflasheado o la VM más económica de un proveedor cloud. La pregunta no es si corre Linux, sino qué combinación de servicios entra sin que el kernel empiece a matar procesos por falta de memoria.
TL;DR
- Con 450 MB de RAM usable, un ARM Cortex-A53 corre 4 a 6 contenedores Podman livianos a la vez sin swap.
- El fork Next-Gen de AdGuard Home bloquea DNS con unos 40 MB fijos, sin importar el tamaño de la lista de bloqueo.
- Beszel vigila CPU y memoria con 20 MB totales: 10 MB de agente y 10 MB de servidor.
- Rootless Podman suma overhead por contenedor vía slirp4netns, así que la cantidad de servicios pesa más que el tamaño de cada imagen.
- systemd-oomd y un swap en zram evitan que un pico de memoria tire todo el stack con hardware tan ajustado.
¿Qué es el self-hosting con poca RAM?
El self-hosting con poca ram es la práctica de correr tus propios servicios (DNS, monitoreo, RSS, túneles) en hardware con muy poca memoria disponible, normalmente menos de 512 MB, usando un sistema operativo liviano y contenedores livianos en lugar de una pila completa tipo Kubernetes.
Comparado con un homelab convencional, el límite real es el presupuesto de memoria. Cada servicio compite por los mismos megabytes, y no hay margen para procesos que reservan memoria de más solo "por si acaso". El OpenStick, con sus cuatro núcleos ARM Cortex-A53 a 1 GHz y 4 GB de almacenamiento eMMC, es un ejemplo extremo, pero el mismo razonamiento aplica a cualquier Raspberry Pi Zero 2 W, a un router con OpenWrt o a la VM más barata de un proveedor cloud.
En ese contexto, Debian aporta un sistema base estable y con paquetes probados, systemd se encarga de arrancar y vigilar cada servicio, y Podman reemplaza a Docker como motor de contenedores sin un daemon corriendo todo el tiempo de fondo.
Por qué importa correr servicios en hardware tan limitado
La mayoría de los tutoriales de self-hosting asumen una máquina con 4 u 8 GB de RAM, que sobra para correr media docena de contenedores sin pensar en el costo real de cada uno. Esa comodidad esconde un problema. Cuando migrás a algo más barato (una VM económica, un dispositivo embebido o un servidor reciclado) los mismos contenedores que "funcionaban bien" empiezan a reiniciarse solos.
Entender qué consume memoria y por qué (el motor de contenedores, el proxy de red de cada servicio, los buffers de journald) permite portar un stack de home server a hardware mucho más barato, o meter más servicios en la misma máquina sin subir de plan. El caso del OpenStick lo deja ver con números concretos: con apenas 450 MB usables corren seis servicios completos.
Cómo funciona el stack: Debian, systemd y Podman
Debian minimal, la instalación "netinst" sin entorno gráfico, ocupa entre 80 y 120 MB de RAM en reposo según qué demonios corran además del kernel. Ese consumo base es el primer costo fijo del self-hosting con poca ram: antes de levantar un solo contenedor, el sistema ya reservó una porción nada despreciable del total disponible.
systemd gestiona el arranque y el ciclo de vida de cada servicio con unidades .service. Desde la versión 243 incluye systemd-oomd, un vigilante de memoria que actúa según la presión de cgroups v2, antes de que el OOM killer del kernel mate procesos a ciegas.
Podman, a diferencia de Docker, no necesita un daemon corriendo en segundo plano: cada contenedor arranca como proceso hijo de conmon, supervisado directamente por systemd. En modo rootless, el recomendado en cualquier dispositivo expuesto a internet, cada contenedor con su propia red usa slirp4netns para NAT de usuario. Eso suma entre 5 y 10 MB de RAM por contenedor solo en overhead de red.
Podman corre sin daemon persistente: cada contenedor es un proceso hijo de conmon.
flowchart TD A["Debian minimal"] --> B["systemd"] B --> C["Podman rootless"] C --> D["conmon + contenedor 1"] C --> E["conmon + contenedor 2"] C --> F["conmon + contenedor N"] B --> G["systemd-oomd"] G -. "mata bajo presion" .-> D G -. "mata bajo presion" .-> E Ejemplos prácticos y cómo empezar
Antes de instalar nada necesitás una Debian 12 (bookworm) o más nueva, acceso root o sudo, y al menos 2 GB libres de almacenamiento para imágenes de contenedor. Estos comandos están probados para Linux (Debian o Raspberry Pi OS). En macOS o Windows la ruta equivalente es instalar Podman Desktop, que corre una VM Linux liviana de fondo.
sudo apt update sudo apt install -y podman podman --version Ese último comando confirma la instalación. La salida esperada es similar a:
podman version 4.9.3 El primer contenedor sirve solo para confirmar que todo el stack (kernel, cgroups, conmon) funciona:
podman run --rm quay.io/podman/hello La salida esperada es el arte ASCII oficial del proyecto:
!... Hello Podman World ...! Project: https://github.com/containers/podman Website: https://podman.io Con eso confirmado, el siguiente paso es un servicio real con un límite de memoria explícito. AdGuard Home es un buen primer caso porque su consumo es predecible: ronda los 40 MB con una lista de bloqueo pequeña, según documenta el caso del OpenStick.
podman run -d --name adguardhome \ --restart=always \ --memory=60m \ -p 53:53/tcp -p 53:53/udp \ -p 3000:3000/tcp \ docker.io/adguard/adguardhome:latest El flag --memory=60m deja un margen de 20 MB sobre el consumo esperado, porque el proceso de instalación inicial carga listas de bloqueo en memoria antes de aplicarlas. Para confirmar que el contenedor respeta ese límite:
podman stats --no-stream adguardhome La columna MEM USAGE / LIMIT debería mostrar algo cercano a 38MiB / 60MiB una vez que el servicio terminó de cargar.
Para que el contenedor sobreviva a un reinicio del dispositivo sin depender de --restart=always, la forma recomendada es generar una unidad systemd con un archivo Quadlet:
mkdir -p ~/.config/containers/systemd cat > ~/.config/containers/systemd/adguardhome.container Seis servicios livianos caben en menos de 170 MB de los 450 MB usables del dispositivo. ## Errores comunes y buenas prácticas El error más frecuente es correr Docker en lugar de Podman porque ya se conoce su sintaxis. El daemon de Docker, solo y sin contenedores, ya consume entre 30 y 60 MB en reposo. En una máquina de 450 MB usables, ese costo fijo equivale a perder uno de los seis servicios de la lista anterior antes de instalar nada. Otro error común es no poner límites explícitos de memoria por contenedor. Sin `--memory`, un contenedor con una fuga o un pico de tráfico puede consumir toda la RAM disponible y arrastrar al resto del sistema con él. Fijar un límite por servicio, aunque sea generoso, convierte un fallo aislado en un problema contenido. El eMMC de 4 GB también es un recurso escaso que se olvida fácil. Journald por defecto guarda logs persistentes en disco sin límite de tamaño. En un dispositivo con flash tan chico conviene limitarlo a memoria volátil: sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/volatile.conf ⚠️ Ojo: el eMMC barato de estos dispositivos tiene un número limitado de ciclos de escritura. Mover journald y cualquier base de datos liviana a memoria volátil alarga la vida del almacenamiento, a costa de perder esos datos en cada reinicio.
Comparativa con alternativas de monitoreo liviano
Para observabilidad en un dispositivo con poca memoria, no todas las opciones cuestan lo mismo. Esta tabla compara las tres rutas más comunes:
OpciónCuándo usarlaVentajaLimitación
BeszelQuerés gráficos de CPU, RAM y disco con alertas, sin complicarte20 MB totales, interfaz lista en minutosMenos protocolos de alerta que una stack completa
Prometheus + GrafanaYa tenés varios servidores y necesitás históricos largosEstándar de facto, miles de dashboards prearmadosSolo Prometheus ya pide 100 a 200 MB en reposo
DozzleSolo querés ver logs de contenedores Podman en vivoInterfaz más cómoda que la CLI para logsNo da métricas de CPU ni RAM como Beszel
En un dispositivo de 450 MB usables, Prometheus y Grafana juntos ya consumen entre un cuarto y un tercio del presupuesto total antes de monitorear nada. Beszel o Dozzle, en cambio, dejan margen para los servicios que en realidad importan.
Profundizando: cgroups, overcommit y por qué cuentan los contenedores
Linux no reserva memoria física para un proceso hasta que efectivamente la escribe. Eso se llama overcommit y explica por qué un contenedor puede arrancar con --memory=200m en una máquina con solo 450 MB totales, pero también por qué dos contenedores "livianos" pueden tumbar el sistema si ambos tocan su límite al mismo tiempo.
Por defecto, vm.overcommit_memory vale 0: el kernel permite reservas de memoria mayores a la física disponible mientras parezcan razonables, y solo falla si la demanda real excede el límite. Para confirmar el modo activo:
cat /proc/sys/vm/overcommit_memory La salida esperada en una instalación default de Debian es 0. Cambiarlo a 2 (contabilidad estricta) en un dispositivo de 512 MB suele ser contraproducente: con tan poca memoria libre, hasta los procesos del propio sistema fallan al reservar el margen que piden por precaución.
cgroups v2, el mecanismo que usa Podman para aplicar límites de memoria, expone el estado real de cada contenedor en /sys/fs/cgroup. Para ver cuánta memoria usa un contenedor específico sin pasar por podman stats:
cat /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/app.slice/*adguardhome*/memory.current Ese número, en bytes, es la métrica que systemd-oomd vigila para decidir a quién matar primero cuando sube la presión de memoria. La política por defecto prioriza al proceso que más excede su propio límite relativo, no al que más memoria absoluta usa.
La razón real por la que importa más la cantidad de contenedores que el tamaño de cada imagen es el overhead fijo por contenedor. Cada uno suma un proceso conmon de 2 a 4 MB, y en modo rootless con red propia, una instancia de slirp4netns de 5 a 10 MB adicionales. Diez contenedores de 5 MB cada uno terminan pesando casi el doble de esos 50 MB nominales solo por overhead de infraestructura.
sequenceDiagram participant U as Usuario participant S as systemd participant P as Podman participant C as Contenedor U->>S: systemctl start adguardhome.service S->>P: arranca conmon y contenedor P->>C: aplica memory.max via cgroups Note over S,C: si memory.current supera el limite S->>C: systemd-oomd envia SIGKILL stateDiagram-v2 [*] --> Normal Normal --> Presion: memoria disponible baja Presion --> Normal: libera memoria Presion --> Matando: supera umbral de oomd Matando --> Normal: contenedor terminado 💡 Tip: agregar swap en zram, memoria comprimida dentro de la propia RAM, le da al kernel un colchón antes de que systemd-oomd tenga que matar nada, sin desgastar el almacenamiento flash.
📖 Resumen en Telegram: Ver resumen
Tu próximo paso: instalá Podman en cualquier Raspberry Pi o VM de 512 MB que tengas a mano y corré podman run --rm quay.io/podman/hello para confirmar que cgroups y el modo rootless funcionan antes de sumar tu primer servicio real.
Preguntas frecuentes
¿Podman reemplaza completamente a Docker en un home server con poca RAM?
Sí, para la mayoría de los casos de self-hosting casero. Podman usa la misma sintaxis de CLI y los mismos formatos de imagen OCI que Docker, pero sin un daemon persistente, lo que ahorra entre 30 y 60 MB fijos.
¿Cuánta RAM necesita Debian minimal antes de correr cualquier servicio?
Entre 80 y 120 MB en reposo con systemd y los demonios básicos activos, según qué paquetes adicionales incluya la imagen base que uses.
¿Por qué usar Beszel en vez de Prometheus y Grafana en un dispositivo de 512 MB?
Porque Prometheus y Grafana juntos ya consumen entre 100 y 200 MB solo para arrancar, mientras que Beszel cubre gráficos de CPU, RAM y disco con 20 MB totales.
¿Qué pasa si un contenedor de Podman supera el límite de memoria que le asigné?
El kernel, vía cgroups, lo mata con un OOM kill dentro de su propio cgroup antes de que afecte al resto del sistema, siempre que hayas fijado --memory al crearlo.
¿Sirve este enfoque de self-hosting con poca ram en una Raspberry Pi Zero?
Sí, el razonamiento es el mismo. Debian o Raspberry Pi OS Lite como base, systemd para supervisar servicios y Podman rootless para contenedores, ajustando los límites de memoria al total real disponible.
¿Hace falta usar swap para que este stack sea estable?
No es obligatorio, pero zram, swap comprimido dentro de la RAM, da un margen extra sin desgastar el almacenamiento eMMC, algo valioso en dispositivos con flash de baja durabilidad.
Referencias
- Bosscher, "What I run on my $10 USD OpenStick homelab": el caso original que documenta los servicios y su consumo de memoria.
- Podman, documentación oficial: instalación, modo rootless y generación de unidades systemd.
- systemd-oomd.service, manual oficial de freedesktop.org: cómo decide systemd qué proceso matar bajo presión de memoria.
- Beszel en GitHub: repositorio del monitor liviano de CPU, RAM y disco.
- AdGuard Home en GitHub: repositorio oficial del bloqueador de DNS a nivel de red.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.


