DevOps

Cómo implementar WordPress en contenedores (Podman)

Podman es la alternativa a Docker de Red Hat/IBM. Daemonless, rootless por diseño, compatible con los comandos de Docker. Para WordPress en producción, Podman rootless es una opción sólida que evita el problema de seguridad de “Docker daemon corre como root”. Esta guía te lleva de cero a un WordPress funcionando con MariaDB, persistencia y HTTPS.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 1-2 horas para el primer multi-stage, 30 min para los siguientes.
  • Requisitos: Docker 24+ con BuildKit habilitado.
  • Resultado: Imágenes 80-95% más pequeñas, builds 50-70% más rápidos.
  • Troubleshooting: DOCKER_BUILDKIT=1, dive para analizar layers, troubleshooting sin shell con stage debug.

Por qué Podman y no Docker

Docker es lo más usado, pero tiene dos problemas reales en producción:

  • Daemon corre como root: si alguien compromete el daemon, tiene root en el host.
  • Licencia: Docker Desktop es pago para empresas > 250 empleados desde 2022.

Podman resuelve ambos:

  • Daemonless: cada comando es un proceso independiente, no hay un servicio central.
  • Rootless por default: pods y contenedores corren con tu usuario, no como root.
  • 100% compatible con CLI de Docker: alias docker=podman y funciona el 90% de los comandos.
  • Open source puro (Apache 2.0): sin licencia enterprise.

Las diferencias operativas:

  • Podman usa pods (grupos de contenedores que comparten red) en vez de solo containers.
  • docker-compose no existe nativo; usas podman-compose o el nuevo podman compose (v4.x+).
  • Algunos flags específicos de Docker no existen (ej: --cap-add tiene equivalentes).

Para WordPress, la migración es casi 1:1. Si sabes Docker, sabes Podman.

Requisitos

  • Linux (Fedora, RHEL, CentOS, Rocky, Ubuntu 22.04+, Debian 12+). macOS con podman machine.
  • 1 GB RAM mínimo para WordPress + MariaDB, 2 GB recomendado.
  • 20 GB de disco mínimo.
  • Podman 4.0+ (para podman compose built-in).

Instalar en Fedora/RHEL:

sudo dnf install -y podman podman-compose

Si ya tienes Docker instalado: podman y docker coexisten sin conflictos. Para no romper scripts existentes que esperan el binario docker, agregá alias docker=podman en tu ~/.bashrc.

En Ubuntu/Debian:

# Ubuntu 22.04+ tiene podman en universe
sudo apt update
sudo apt install -y podman
# Si la versión es vieja (< 4.0) y la de universe es < 4.0
. /etc/os-release
curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key 
  | sudo gpg --dearmor --yes -o /etc/apt/keyrings/devel_kubic_libcontainers_stable.gpg
echo "deb [signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_stable.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" 
  | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
sudo apt update
sudo apt install -y podman

Verificar:

podman --version
# podman 4.x.x
# Test básico
podman run --rm hello-world

Si hello-world no descarga (timeout o error de registry), verifica DNS y mirror: podman info | grep -i registry. Para sistemas offline, configura mirror en /etc/containers/registries.conf.

Si podman --version no devuelve nada, el binario no está en PATH. verifica con which podman o rpm -ql podman | grep bin/ (RHEL/Fedora) o dpkg -L podman | grep bin/ (Debian/Ubuntu).

Setup rootless

Podman rootless necesita subuid/subgid mapeados para tu usuario:

# Verificar
cat /etc/subuid /etc/subgid
# tu-usuario:100000:65536

Si no aparece tu usuario, agregar:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 tu-usuario

Límites importantes del modo rootless:

  • No puedes bindear puertos < 1024 directamente (usar unshare o proxy reverso).
  • Algunos filesystem mounts requieren ajustes.
  • Cgroups v2 con delegación habilitada.

WordPress + MariaDB con Podman

Estructura de directorios

mkdir -p ~/wordpress-podman/{data,db,php-conf}
cd ~/wordpress-podman

docker-compose.yml (compatible con podman-compose)

version: "3.8"
services:
  db:
    image: mariadb:11
    container_name: wordpress-db
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./db:/var/lib/mysql:Z
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
  wordpress:
    image: wordpress:6-php8.3-apache
    container_name: wordpress-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_CONFIG_EXTRA: |
        define('FS_METHOD', 'direct');
    volumes:
      - ./data:/var/www/html:Z
    ports:
      - "8080:80"
  proxy:
    image: caddy:2-alpine
    container_name: wordpress-proxy
    restart: unless-stopped
    depends_on:
      - wordpress
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:Z
      - caddy_data:/data
      - caddy_config:/config
volumes:
  caddy_data:
  caddy_config:

.env (credenciales)

cat > .env <<'EOF'
DB_ROOT_PASSWORD=tu-root-password-seguro
DB_PASSWORD=tu-db-password-seguro
EOF
chmod 600 .env

Caddyfile (reverse proxy con HTTPS automático)

tu-dominio.com {
    reverse_proxy wordpress-app:80
    encode gzip
    log {
        output stdout
    }
}

Caddy pide certificado HTTPS a Let's Encrypt automáticamente si el dominio apunta al servidor. Para localhost, usa cert autofirmado.

Levantar el stack

# Con podman-compose
podman-compose up -d
# O con el nuevo podman compose (4.x+)
podman compose up -d

Si el stack arranca pero los containers exit-an inmediatamente, revisa logs con podman-compose logs -t nombre_servicio. Errores comunes: variables de entorno mal pasadas, puertos ya en uso, falta de imagen base.

Verificar:

podman ps
# CONTAINER ID  IMAGE                       STATUS
# abc123        docker.io/library/wordpress Up
# def456        docker.io/library/mariadb   Up
# ghi789        docker.io/library/caddy      Up
# Logs
podman logs -f wordpress-app
# Acceso
http://localhost:8080  # directo
http://tu-dominio.com  # vía Caddy

WordPress setup wizard te guía los primeros pasos.

Persistencia

Lo crítico: dónde viven los datos. Si no entiendes esto, un podman-compose down -v te borra todo.

# Datos de WordPress (uploads, plugins, themes, wp-config.php custom)
./data/
# Base de datos MariaDB
./db/
# Certificados y config de Caddy
volumenes nombrados caddy_data, caddy_config

Los volúmenes nombrados (caddy_data, caddy_config) sobreviven a down, pero -v los borra. Para backups, los directorios bind-mount (./data, ./db) son más fáciles de copiar.

Backup simple:

# Backup completo
tar -czf wordpress-backup-$(date +%Y%m%d).tar.gz ./data ./db .env
# O con mariadb-dump
podman exec wordpress-db mariadb-dump -u root -p"${DB_ROOT_PASSWORD}" wordpress > db-dump-$(date +%Y%m%d).sql

Restore:

podman exec -i wordpress-db mariadb -u root -p"${DB_ROOT_PASSWORD}" wordpress < db-dump.sql

Networking entre contenedores

Compose crea una red con DNS interno. Los servicios se ven por nombre (db, wordpress, proxy). El DNS resuelve automáticamente.

Verificar:

podman exec wordpress-app getent hosts db
# db  10.89.1.3

Si quieres exponer un puerto individual:

ports:
  - "127.0.0.1:8080:80"   # solo localhost
  - "8080:80"              # todas las interfaces

En rootless, no puedes bindear < 1024. Workaround:

# Option 1: usé el puerto alto en compose y mappealo a 80 con Caddy
# Option 2: usar capabilities
sudo setcap cap_net_bind_service=+ep /usr/bin/podman
# Option 3: ports en compose son 8080, Caddy hace de proxy y bindea 80

La opción 3 es la más común y limpia: solo Caddy bindea 80/443, los demás servicios están en la red interna.

Pods vs containers

Docker tiene containers. Podman tiene pods (group of containers) y containers. Un pod comparte network namespace (mismo IP, mismo localhost) y opcionalmente volumes.

Para WordPress + MariaDB, pod NO es necesario (cada uno en su container con DNS). Pod es útil para:

  • Logs collector + app
  • App + sidecar de monitoreo
  • App + proxy

Si quieres usar pods:

# Crear pod con WordPress y MariaDB
podman pod create --name wordpress-pod -p 8080:80
# Levantar dentro del pod
podman run -d --pod wordpress-pod --name db -e MARIADB_ROOT_PASSWORD=secret mariadb:11
podman run -d --pod wordpress-pod --name wordpress --env WORDPRESS_DB_HOST=127.0.0.1 wordpress:6

Equivalente al compose pero usando el concepto de pod. Para WordPress, no aporta mucho sobre compose.

Volúmenes y permisos

El :Z al final de los bind mounts es importante en rootless + SELinux:

volumes:
  - ./data:/var/www/html:Z

El :Z re-etiqueta el contexto SELinux para que el container pueda acceder. Sin él, en Fedora/RHEL con SELinux enforcing, vas a ver "Permission denied" en los logs.

Permisos comunes:

# WordPress corre como www-data (uid 33) en el container
# Tu usuario en el host es tu-usuario (uid 1000)
# Para que ambos escriban, opciones:
# 1. Que el container escriba todo (recomendado)
# No hace falta ajustar host, dejar que WP se autocree estructura
# 2. ACL (más flexible)
sudo setfacl -R -m u:1000:rwx -m u:33:rwx ./data
# 3. chown (cambia owner al uid del container)
sudo chown -R 33:33 ./data  # pero perdés acceso desde host

La opción 1 es la más simple para sitios que se manejan desde el admin de WP. La opción 2 si editas archivos desde el host.

Logs

# Logs de un container
podman logs wordpress-app
# Follow
podman logs -f wordpress-app
# Con timestamp
podman logs -t wordpress-app
# Desde un tiempo
podman logs --since 10m wordpress-app

Logs de systemd (rootless en Fedora/RHEL):

journalctl --user -u podman
journalctl --user CONTAINER_ID=abc123

Actualizar WordPress y plugins

Hay dos caminos: actualizar la imagen (cambio de versión de WP) o actualizar desde el admin de WP (cambia plugins/themes).

Actualizar la imagen:

podman-compose pull
podman-compose up -d  # recrea containers con la nueva imagen

WordPress hace la migración de DB automáticamente al primer request (5-30 segundos). Los uploads en ./data persisten.

Actualizar desde admin es lo normal para plugins/themes. Combinado con cron:

# Cron de WP: definir WP-CRON en lugar de cron del sistema
# En wp-config.php:
define('DISABLE_WP_CRON', true);
# Cron del host:
0 */6 * * * curl -s http://tu-sitio.com/wp-cron.php > /dev/null

Más robusto que el WP-Cron default.

Troubleshooting

"Permission denied" en uploads

SELinux. Solución:

# Agregar :Z a los bind mounts
chcon -R -t container_file_t ./data ./db

O desactivar SELinux temporalmente para debug (no en producción):

sudo setenforce 0

"Cannot connect to database"

El container wordpress no resuelve db. Verificar:

# Están en la misma red?
podman network ls
podman network inspect wordpress-podman_default
# El container db tiene la variable de DB_NAME=wordpress?
podman exec wordpress-db env | grep MARIADB

El sitio va lento

  • Cache de WP deshabilitado: instalar WP Super Cache o W3 Total Cache.
  • DB con muchos queries: instalar Query Monitor plugin.
  • Disco lento: si usas HDD, considerar SSD para ./db.
  • Memoria insuficiente: ver docker stats o podman stats.

"Out of disk space"

Limpiar imágenes viejas:

podman system prune -a

Cuidado: -a borra TODAS las imágenes no usadas. Si tienes otros containers, mejor selectivo:

podman image prune

El container se reinicia solo

podman logs --tail 100 wordpress-app
# buscar "Fatal" o "Out of memory"

OOM es común. Subir memoria en el compose o limitar otros servicios:

deploy:
  resources:
    limits:
      memory: 1G

Producción: cosas que faltan

Para producción real, agregar:

  • HTTPS: ya está con Caddy + Let's Encrypt.
  • Backups automatizados: cron + mariadb-dump + tar del directorio.
  • Monitoreo: Prometheus + cAdvisor, o Uptime Kuma.
  • Updates: Watchtower o cron que corra podman-compose pull && up -d semanal.
  • Firewall: ufw/nftables abriendo solo 80, 443, 22.
  • Fail2ban: proteger wp-login.php.
  • WAF: ModSecurity + OWASP rules.
  • CDN: Cloudflare delante para cachear assets.
  • Múltiples réplicas: para alta disponibilidad, usar Kubernetes o Nomad.

Migrar de Docker a Podman

Si ya tienes un setup Docker, migrar es:

# Detener todo
docker-compose down
# Cambiar alias
alias docker=podman
# Levantar con podman
podman-compose up -d

Verificar imágenes: Podman puede usar las imágenes de Docker Hub directo. Los nombres cambian ligeramente (docker.io/library/wordpress vs wordpress), pero el resultado es el mismo.

Para compose files complejos, podman-compose tiene limitaciones vs docker-compose. Si usas features avanzados (build args, secrets, networks custom), revisa la compatibilidad. La mayoría de setups simples migran sin tocar nada.

Conclusión

Podman es una alternativa sólida a Docker para WordPress, especialmente en Fedora/RHEL donde la integración es nativa. El modo rootless resuelve el problema de seguridad del Docker daemon, y los comandos son familiares.

Para empezar: instalar podman, levantar el stack compose, configurar Caddy con tu dominio. Para producción: agregar backups, monitoring, y los hardening usuales.

FAQ

¿Podman es 100% compatible con Docker? Comandos CLI, sí. Algunas features avanzadas de Docker (Docker Swarm, BuildKit con ciertas configs) no tienen equivalente directo. Para WordPress y la mayoría de apps, compatibilidad total.

¿Vale la pena migrar de Docker a Podman? Si Docker te funciona y no tienes problemas, no. Si te preocupa seguridad (rootless) o licensing enterprise, sí.

¿Qué imagen uso, wordpress o wordpress:6-php8.3-apache? La segunda. Pin la versión de PHP y de WP. wordpress:latest puede romper cuando actualizan a PHP 9 o cambian defaults.

¿Cómo hago HTTPS en localhost? Caddy con localhost se auto-firma. Para dev, mkcert y configuración manual. Para prod, dominio real + Caddy + Let's Encrypt.

¿Funciona con Mac (Apple Silicon)? Sí, con podman machine. Hay algunas diferencias de performance vs Linux pero funciona. La integración es menos pulida que Docker Desktop en Mac.

¿Es más rápido o más lento que Docker? Muy similar. Podman es daemonless, así que el primer comando puede tardar más (inicializa el container runtime). Después, performance idéntica.

¿Cómo actualizo Podman? Mismo método que instalaste: dnf update podman o apt upgrade podman.

Fuentes

  • https://docs.podman.io/
  • https://github.com/containers/podman
  • https://docs.podman.io/en/latest/Guides/Compose.html
  • https://hub.docker.com/_/wordpress
  • https://hub.docker.com/_/mariadb
  • https://caddyserver.com/docs/

Preguntas frecuentes

¿Por qué Cómo implementar WordPress en contenedores (Podman) es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con Cómo implementar WordPress en contenedores?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con Cómo implementar WordPress en contenedores?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar Cómo implementar WordPress en contenedores y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a Cómo implementar WordPress en contenedores?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]