Por qué una checklist antes de producción
Docker Compose se diseñó para el desarrollo: sus valores por defecto priorizan la simplicidad, no la robustez. Un contenedor no se reinicia tras un reinicio del host, sus logs crecen sin límite, puede consumir toda la RAM de la máquina y escucha en todas las interfaces de red. En desarrollo, ninguno de estos comportamientos supone un problema, porque usted relanza la stack a mano varias veces al día. En producción, esos ajustes ausentes se convierten en incidentes: disco lleno a las tres de la madrugada, base de datos perdida tras un docker compose down, servicio inaccesible tras un corte de corriente. La buena noticia: endurecer una stack Compose no exige reescribirla, solo una decena de añadidos concretos. Repase cada punto de esta lista antes de publicar y su servicio aguantará la carga y los reinicios sin vigilancia constante.
Los 10 puntos de un vistazo
- restart: unless-stopped — el contenedor se reinicia tras un reinicio del host o un fallo
- healthcheck — Docker detecta un contenedor bloqueado y permite reinicios progresivos
- límites de CPU/memoria — un servicio desbocado ya no puede asfixiar a sus vecinos
- secretos vía .env o Docker secrets — nunca una contraseña en claro en el archivo Compose
- volúmenes con nombre — los datos sobreviven a un
docker compose down - rotación de logs —
max-sizeymax-fileimpiden que el disco se llene - aislamiento de red — separe
frontendybackend, no exponga todo en el bridge por defecto - binding de puertos —
127.0.0.1:PORTdetrás de un reverse proxy, no0.0.0.0 - tags de imagen fijados — una versión o un digest, nunca
latest - depends_on con condición —
service_healthyevita las carreras en el arranque
Requisitos previos
Antes de aplicar esta checklist, asegúrese de disponer de un VPS con Docker Engine y el plugin Compose v2 instalados (el comando es docker compose, sin guion, desde 2022). Compruebe la versión con docker compose version: la sintaxis deploy.resources fuera de Swarm exige Compose v2. Coloque su archivo en una carpeta de proyecto dedicada, por ejemplo /opt/monapp, con un archivo .env al lado y permisos restringidos (chmod 600 .env). Prevea un reverse proxy por delante — Traefik, Caddy o Nginx — porque varios ajustes, en particular el binding de puertos, dan por supuesto que el tráfico público nunca llega directamente a sus contenedores. Por último, mantenga una copia de su archivo Compose bajo control de versiones.
Los 10 pasos en detalle
1. Política de reinicio
Añada
restart: unless-stoppeda cada servicio. El contenedor se reinicia tras un fallo o un reinicio del host, pero permanece detenido si usted lo paró voluntariamente. Eviterestart: always, que relanzaría incluso un contenedor que quería dejar parado.2. Healthcheck
Declare un bloque
healthcheck:con untest(por ejemplocurl -f http://localhost:8080/health || exit 1), uninterval, untimeouty unosretries. Docker marca entonces el contenedor comohealthyounhealthy, lo que permite a los demás servicios reaccionar ante un bloqueo.3. Límites de recursos
En
deploy.resources.limits, fijecpusymemory(por ejemplomemory: 512M). Sin límite, un servicio con una fuga puede consumir toda la RAM y hacer que el OOM killer mate a los demás. Añada tambiénreservationspara garantizar un mínimo.4. Secretos fuera del archivo
Nunca ponga una contraseña en claro en el YAML. Refiéralas mediante
env_file: .envo${VARIABLE}, o utilice el mecanismosecrets:de Docker, que monta el secreto como archivo dentro del contenedor. Añada.enva su.gitignore.5. Volúmenes con nombre
Declare sus datos en volúmenes con nombre y con un driver explícito, en lugar de un bind-mount anónimo. Un volumen con nombre sobrevive a
docker compose down; solodown -vlo borra. Documente cada volumen para saber de qué hace copia de seguridad.6. Rotación de logs
Añada un bloque
logging:condriver: json-filey las opcionesmax-size: "10m"ymax-file: "3". Sin ello, los logs de un contenedor hablador llenan el disco hasta provocar la caída. Aplíquelo a cada servicio.7. Aislamiento de red
Cree redes con nombre — una
frontendpara lo que está expuesto, unabackendpara la base de datos — y conecte cada servicio solo a las redes que necesita. Su base de datos únicamente debe ser accesible desde la aplicación, nunca desde el bridge por defecto compartido.8. Binding de puertos
Detrás de un reverse proxy, publique en
127.0.0.1:8080:8080y no en8080:8080(que equivale a0.0.0.0). De lo contrario, el puerto sigue accesible desde Internet pese al proxy, esquivando sus reglas de TLS y de autenticación.10. Orden de arranque
Utilice
depends_onconcondition: service_healthypara que un servicio no espere solo al lanzamiento, sino a la disponibilidad real de su dependencia. Esto supone un healthcheck en el servicio dependiente (paso 2) y elimina las carreras en el arranque.
Valores por defecto de dev frente a ajustes de prod
Desplace la tabla
| Parámetro | Por defecto en dev | Recomendado en prod |
|---|---|---|
| restart | no | unless-stopped |
| healthcheck | ausente | definido con interval y retries |
| memoria | sin límite | limit fijado (p. ej. 512M) |
| secretos | en claro posible | .env o Docker secrets |
| volúmenes | anónimos | con nombre y con driver |
| logs | sin límite | max-size + max-file |
| red | bridge por defecto | frontend / backend separados |
| puertos | 0.0.0.0 | 127.0.0.1 detrás de proxy |
| imagen | latest | versión o digest fijado |
| depends_on | solo arranque | condition: service_healthy |
Pruebe siempre su stack endurecida en local antes de llevarla a producción: lance docker compose config para validar la sintaxis, después docker compose up, y simule un reinicio con docker compose restart. Compruebe que los contenedores vuelven a estar healthy y que los datos persisten tras un down seguido de un up.
Solución de problemas
Si un contenedor se queda bloqueado en estado starting, su healthcheck está fallando: pruebe el comando test a mano con docker compose exec service sh y compruebe que devuelve 0. Un servicio que se reinicia en bucle (Restarting) suele esconder un error en el arranque — consulte docker compose logs -f service. Si deploy.resources.limits parece ignorarse, recuerde que en Compose v2 fuera de Swarm los límites sí se aplican, pero las reservations solo lo hacen en modo Swarm. Un puerto que sigue accesible pese a 127.0.0.1 suele señalar un cortafuegos ausente o una regla de Docker que esquiva UFW — compruébelo con ss -tlnp. Por último, si un docker compose down ha borrado sus datos, casi siempre es porque quedaba un -v de más o porque el volumen era anónimo en lugar de tener nombre.
CVE-2026-17106 (CopyEscape): actualizar Docker Engine
CVE-2026-17106, bautizada CopyEscape, es una race condition en docker cp divulgada el 10 de agosto de 2026. Un contenedor no confiable puede producir un archivo tar incoherente que sigue un enlace simbólico fuera del destino, lo que acaba sobrescribiendo un archivo arbitrario en el host — incluido el binario runc. La superficie: todo VPS que ejecute docker cp desde un contenedor cuyo contenido usted no controla. La corrección está disponible en Docker Engine ≥ 29.7.2 y Docker Desktop ≥ 4.86.0. Compruebe su versión con docker version y actualice antes de exponer un nuevo servicio. Si no puede actualizar de inmediato, evite docker cp desde contenedores no confiables y aplique el principio de mínimo privilegio (--cap-drop ALL).
Gestión de secretos de Docker: rotación sin downtime
Las variables de entorno en claro dentro de un archivo Compose son la primera fuga de secretos en producción: aparecen en docker inspect, en los logs de error y en los volcados de proceso. El mecanismo secrets: de Docker — o un gestor externo como Vault o Infisical — monta los secretos en forma de archivos en /run/secrets/, fuera del alcance de inspect. Para una rotación sin downtime, el método recomendado consiste en versionar el secreto (crear un nuevo secreto db_password_v2 en paralelo al v1), actualizar el servicio para que lea v2, redesplegar en rolling update (docker compose up -d --no-deps service) y después eliminar v1 una vez validado el despliegue. Ninguna interrupción de servicio, ninguna ventana de secreto expuesto entre las dos versiones.
Hacer copias de seguridad de los volúmenes sin corrupción
Copiar los archivos de un volumen PostgreSQL o MySQL en caliente con rsync o tar produce casi con toda seguridad una copia de seguridad corrupta: el motor escribe de forma continua durante la copia y las páginas de datos se capturan en puntos de control distintos. La regla es hacer siempre un dump SQL antes del snapshot del volumen: pg_dump o mysqldump producen un estado coherente que usted puede archivar o transferir. Para automatizar este enfoque en un VPS con Docker, herramientas como Restic y Offen Docker Backup orquestan la congelación de la base de datos, el dump SQL, el snapshot cifrado y el envío a un almacenamiento remoto. Documente cada volumen con nombre (paso 5 de la checklist) y asócielo a una estrategia de restauración probada: una copia de seguridad sin prueba de restauración es un dato no respaldado.
Conclusión
Estos diez ajustes convierten un archivo Compose de desarrollo en una stack de producción capaz de sobrevivir a los reinicios, a los picos de carga y a las noches sin vigilancia. Ninguno exige una herramienta adicional: todo cabe en el YAML que usted ya tiene. Adquiera la costumbre de recorrer esta checklist antes de cada puesta en línea, idealmente en forma de revisión de docker compose config integrada en su despliegue. Una vez asentados estos cimientos, puede añadir un reverse proxy como Traefik o Caddy, o una capa de orquestación como Coolify, con total confianza.