Guía: checklist técnico para preparar un VPS Debian con Docker Compose para aplicaciones PHP en producción
Por Kevin Marcondes · Publicado el · 9 min de lectura
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 checklistVer también
- Guía práctica: configurar failover activo‑pasivo entre dos VPS con Keepalived, HAProxy y lsyncd para uploads sincronizados
- Guía práctica para dimensionamiento de VPS y tuning de PHP‑FPM para tiendas Magento/OpenMage con picos estacionales: del dimensionamiento a la prueba de carga
- Guía: cómo planificar y ejecutar pruebas de carga en aplicaciones PHP monolito usando k6 y Docker Compose — paso a paso
- Guía de backup y recuperación PostgreSQL con wal‑shipping y basebackup en VPS con espacio limitado: práctica de bajo uso de disco