Guía: checklist técnico para preparar un VPS Debian con Docker Compose para aplicaciones PHP en producción

Por · Publicado el · 9 min de lectura

Panel de parcheo con conectores y cables de fibra óptica instalados en un rack
Visual profesional de infraestructura de red para entornos de servidores y VPS. Foto: Brett Sayles / Pexels

Guía: checklist técnico para preparar un VPS Debian con Docker Compose para aplicaciones PHP en producción. Este paso a paso cubre sysctl, ulimits, journald, swap y cgroups. Aplica los ajustes, valida con comandos sencillos y reduce riesgos de OOM, pérdida de logs y cuellos de botella en red o descriptores de archivos.

Qué cubre este checklist y cuándo usarlo

El checklist se centra en la preparación del host Debian y del Docker para monolitos PHP o stacks PHP en Compose. Prioriza estabilidad, previsibilidad y observabilidad antes de optimizaciones puntuales.

Úsalo al iniciar una VPS, migrar hosts o estandarizar entornos. También sirve para reducir incidentes por OOM, logs sin retención y límites del sistema.

Aplica primero en staging. Cambios en el kernel, cgroups y en el servicio Docker pueden afectar a todos los contenedores en ejecución.

  • Host Debian 11/12 con systemd y Docker instalado
  • Docker Compose v2 (CLI docker compose)
  • Stacks PHP con servicios separados (web, php‑fpm, base de datos, cache)

Prerequisitos y versiones recomendadas

La combinación de kernel, systemd y Docker define cgroups, logs y límites. En Debian reciente, cgroup v2 es estándar y Docker usa el driver de cgroup systemd.

Revisa estos puntos antes de continuar. Asegura espacio en /var para journald y privilegios de root.

  • Kernel y systemd actualizados con cgroup v2
  • Docker Engine 20.10+ y Compose v2
  • Acceso root al host y espacio libre en /var
# Verificar cgroup v2
stat -fc %T /sys/fs/cgroup  # esperado: cgroup2fs

# Verificar driver de cgroup do Docker
docker info | grep -i 'Cgroup Driver'  # esperado: systemd

# Versões
docker --version
docker compose version

Qué sysctl ajustar en Debian para Docker/PHP

Algunos parámetros del kernel evitan saturación de colas, agotamiento de puertos efímeros y límites de conexiones. Configure vía /etc/sysctl.d para persistencia y cárguelos sin reboot.

Si notas intermitencias en el bridge de Docker, investiga red y colas. Consulta causas y correcciones en caída de conexión en el bridge de Docker.

Los valores siguientes son un punto de partida. Ajústalos según memoria, tráfico y patrón de conexiones de tu aplicación.

  • Habilitar enrutamiento para redes Docker
  • Ampliar backlog y SYN backlog
  • Expandir puertos efímeros para muchas conexiones salientes
  • Dimensionar conntrack según carga y memoria
# /etc/sysctl.d/99-docker-php.conf
net.ipv4.ip_forward = 1
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.ip_local_port_range = 1024 65535

# Conntrack: comece moderado e ajuste com métricas
net.netfilter.nf_conntrack_max = 131072

# Aplicar agora
sysctl --system

# Checar
sysctl -n net.ipv4.ip_forward net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.ip_local_port_range net.netfilter.nf_conntrack_max

Cómo configurar ulimits en el host y en los contenedores

Límites de archivos y procesos evitan errores como "Too many open files". En servicios gestionados por systemd, usa overrides. En contenedores, define ulimits en Compose.

Asegura que el límite del host sea mayor que el del contenedor. Si no, Docker no aplicará el valor solicitado. Reiniciar el servicio docker interrumpe contenedores en ejecución. Para diagnóstico de FDs en PHP‑FPM, consulta cómo resolver el error 'Too many open files'.

  • Aumenta fs.file-max y LimitNOFILE en docker.service
  • Define ulimits por servicio en Compose
  • Alinea límites con PHP‑FPM (pm.max_children, sockets, conexiones)
# 1) Limite global de FDs do kernel
sysctl -w fs.file-max=500000
# Para persistir: adicione fs.file-max = 500000 em /etc/sysctl.d/99-docker-php.conf

# 2) Override do systemd para o Docker
mkdir -p /etc/systemd/system/docker.service.d
cat >/etc/systemd/system/docker.service.d/override.conf <<'EOF'
[Service]
LimitNOFILE=1048576
LimitNPROC=131072
EOF
systemctl daemon-reload && systemctl restart docker

# 3) docker-compose.yml (exemplo por serviço)
# services:
#   php:
#     image: your-php-image:latest
#     ulimits:
#       nofile: 65536
#       nproc: 4096
#       core: 0

# Diagnóstico de FDs em runtime
# docker compose exec php sh -lc 'ulimit -n; ulimit -u'

Journald: retención, etiquetas e impacto en disco

Usar journald como log-driver centraliza logs y facilita retención con compresión. Define cuotas y retención para evitar saturar el disco de la VPS.

Estandariza la etiqueta (tag) de los contenedores para filtros consistentes. Para detalles de rotación y compresión en VPS con poco disco, consulta la retención de logs con journald + logrotate + rsyslog.

  • Configura Storage=persistent para mantener logs tras reboot
  • Limita SystemMaxUse y MaxRetentionSec
  • Usa log-driver=journald con tag consistente
# /etc/systemd/journald.conf (trecho)
[Journal]
Storage=persistent
SystemMaxUse=1G
SystemMaxFileSize=128M
MaxRetentionSec=14day
RateLimitIntervalSec=30s
RateLimitBurst=10000

systemctl restart systemd-journald
journalctl --disk-usage

# /etc/docker/daemon.json (trecho)
{
  "log-driver": "journald",
  "log-opts": {"tag": "{{.Name}}"},
  "exec-opts": ["native.cgroupdriver=systemd"]
}

systemctl restart docker && sleep 2 && docker info | grep -i log

Swap y OOM: ¿cuál es la estrategia segura para producción?

Una pequeña área de swap reduce OOM kills en picos cortos de memoria. En workloads PHP, mantiene swappiness bajo y monitoriza page faults.

Crea swapfile si falta swap y valida. Swap en disco lento puede aumentar la latencia. Para entender picos nocturnos de I/O y swap, consulta picos de I/O y swap en VPS.

  • Crea swapfile proporcional a la RAM y al tráfico
  • Ajusta vm.swappiness a 10–20 como punto de partida
  • Alinea memswap con mem_limit en contenedores críticos
# Criar swapfile (ex.: 2G)
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

# Reduzir swappiness
sysctl -w vm.swappiness=10
# Persistir em /etc/sysctl.d/99-docker-php.conf
# vm.swappiness = 10

# Conferir swap
swapon --show
cat /proc/sys/vm/swappiness

# Ajuste por container no compose (evita uso de swap pelo container)
# services:
#   php:
#     mem_limit: 1g
#     memswap_limit: 1g  # total = memória + swap; igual à mem_limit ≈ sem swap no container

Cgroups v2 y Docker Compose: cómo limitar recursos

Cgroups v2 expone límites de CPU, memoria, PIDs e I/O. En Compose local, la clave deploy.* se ignora; usa campos específicos de límites.

Valida el driver de cgroup systemd y revisa los límites vía docker inspect y archivos en /sys/fs/cgroup. Para un paso a paso completo, consulta cgroup v2 y systemd ResourceControl en Docker.

  • Usa mem_limit, memswap_limit, cpus y pids_limit en Compose
  • Ajusta blkio_config para no asfixiar el disco
  • Evita deploy.resources en Compose sin Swarm
# docker-compose.yml (exemplo simplificado)
version: "3.9"
services:
  php:
    image: your-php-image:latest
    cpus: "1.5"
    mem_limit: 1g
    memswap_limit: 1g
    pids_limit: 4096
    ulimits:
      nofile: 65536
      nproc: 4096
    blkio_config:
      weight: 600

# Verificações
# docker compose up -d
# docker inspect --format '{{json .HostConfig.NanoCpus}}' <container>
# docker compose exec php sh -lc 'cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory.limit_in_bytes'

Verificaciones rápidas y automatización del checklist

Después de los ajustes, ejecuta verificaciones objetivas. Confirman sysctl, límites, logs y cgroups de forma repetible.

Automatiza con Ansible o scripts. Si un valor difiere, corrígelo en los archivos persistentes: sysctl.d, daemon.json y overrides de systemd.

Elemento Comando Salida esperada
Cgroup v2 stat -fc %T /sys/fs/cgroup cgroup2fs
Driver de cgroup de Docker docker info | grep -i 'Cgroup Driver' systemd
Puertos efímeros sysctl -n net.ipv4.ip_local_port_range 1024 65535
Backlogs (somaxconn/SYN) sysctl -n net.core.somaxconn net.ipv4.tcp_max_syn_backlog >= 1024 / >= 1024
Conntrack sysctl -n net.netfilter.nf_conntrack_max > 65536 (ajustar à carga)
Swap y swappiness swapon --show; cat /proc/sys/vm/swappiness swap activo; 10–20
Retención de journald journalctl --disk-usage <= cota definida (ex.: ~1G)
Límite de FDs (host) cat /proc/sys/fs/file-max >= 500000 (ajustar conforme)
Límite de FDs (contenedor) docker compose exec php sh -lc 'ulimit -n' >= 65536
# Validação resumida
set -euo pipefail
printf "Cgroup: "; stat -fc %T /sys/fs/cgroup
printf "Cgroup Driver: "; docker info 2>/dev/null | awk -F: '/Cgroup Driver/{print $2}'
printf "Ports: "; sysctl -n net.ipv4.ip_local_port_range
printf "Backlogs: "; sysctl -n net.core.somaxconn net.ipv4.tcp_max_syn_backlog
printf "Conntrack: "; sysctl -n net.netfilter.nf_conntrack_max
printf "Swap: "; swapon --show | tail -n +2 | wc -l; cat /proc/sys/vm/swappiness
journalctl --disk-usage || true
printf "FDs host: "; cat /proc/sys/fs/file-max
# FDs container (requer serviço 'php')
docker compose exec -T php sh -lc 'echo -n "FDs container: "; ulimit -n' || true

Preguntas frecuentes

Qué swappiness usar en producción para PHP en Docker?

Usa swappiness bajo, entre 10 y 20, para reducir intercambios agresivos y latencia. Mantén una pequeña área de swap para amortiguar picos cortos de memoria. Monitoriza OOM kills y page faults y ajusta según el perfil de tráfico y la RAM del host.

Es mejor usar journald o json-file para logs de contenedores?

Para VPS, journald facilita retención y compresión con el mismo mecanismo del host. Evita crecimiento descontrolado del json-file. Si usas json-file, aplica max-size y max-file. Con journald, define SystemMaxUse y retención y monitoriza con journalctl --disk-usage.

Funciona deploy.resources del Compose fuera de Swarm?

No. deploy.resources se ignora en el Compose tradicional. Para imponer límites, usa cpus, mem_limit, memswap_limit, pids_limit, blkio_config y ulimits. Valida con docker inspect y leyendo archivos en /sys/fs/cgroup dentro del contenedor.

Qué valor de nofile debo definir para PHP‑FPM?

Empieza con 65536 por contenedor PHP y ajusta según métricas. Considera conexiones simultáneas, sockets, ficheros y logs. En el host, mantiene fs.file-max y LimitNOFILE muy por encima de los valores de los contenedores. Mide el pico real de FDs en horarios críticos.

Cómo evitar que los logs ocupen todo el disco de la VPS?

Limita journald con SystemMaxUse y MaxRetentionSec y estandariza tags. Si usas json-file, configura max-size y max-file. Monitoriza con journalctl --disk-usage y crea alertas antes del 80% de uso de disco. Limpiezas periódicas de imágenes y volúmenes también ayudan.

Debo desactivar completamente la swap en producción?

Generalmente, no. Una pequeña swap ayuda a evitar OOM kills en picos breves. Desactivarla aumenta el riesgo de reinicios de procesos. Prefiere swappiness bajo y límites claros por contenedor (mem_limit y memswap_limit) para controlar consumo sin asfixiar el host.

¿Quieres revisar tu checklist antes del deploy?

Envía tu docker-compose.yml y las salidas de las verificaciones. Podemos revisar límites, sysctl y journald para un arranque más tranquilo.

Hablar sobre el checklist

Ver también