Docker Compose: iniciar contenedores al arrancar
Configura restart: always o unless-stopped para recuperar servicios tras un reinicio. on-failure no cubre todos los arranques; usa systemd si importa el orden.
La respuesta breve
Los servicios de Docker Compose se inician durante el arranque cuando se cumplen dos condiciones al mismo tiempo. El daemon de Docker debe estar habilitado como servicio del sistema, y cada servicio del archivo debe tener una política de reinicio unless-stopped o always. Añada restart: unless-stopped a cada servicio, ejecute docker compose up -d una vez y los contenedores volverán a iniciarse automáticamente después de un reinicio. No se necesita nada más en el caso habitual.
Solo necesita una unidad de systemd cuando el orden es importante: por ejemplo, una pila que depende de un disco montado, una interfaz VPN o un recurso compartido de red que no está listo cuando se inicia el daemon de Docker. Este caso es real y se explica en la segunda mitad de esta guía. Si todavía está aprendiendo a definir servicios y volúmenes, empiece por los conceptos básicos de Docker Compose en un VPS y vuelva después.
Establecer la política de reinicio en compose.yaml
La política se define una vez por servicio. No existe un interruptor global. Por eso, un servicio que se omita permanecerá detenido después del reinicio, mientras el resto de la pila se inicia.
services:
app:
image: nginx:1.27
restart: unless-stopped
ports:
- "8080:80"
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: change-me
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:Aplícala y, después, lee la política del contenedor en ejecución:
docker compose up -d
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app)Esto muestra unless-stopped. Si muestra no, se editó el archivo, pero el contenedor nunca se volvió a crear.
Este es el error más común. La política de reinicio se almacena en el contenedor, no en el archivo YAML. Editar compose.yaml no cambia nada en un contenedor que ya existe. docker compose restart tampoco sirve, porque detiene e inicia el mismo objeto de contenedor sin modificar su configuración. Solo docker compose up -d compara el archivo con los contenedores en ejecución, detecta que la política cambió y los vuelve a crear.
Para un contenedor que no quieras volver a crear ahora, cambia la política directamente:
docker update --restart unless-stopped my-containerEdita también el archivo YAML. docker update cambia el contenedor en ejecución, y el siguiente docker compose up -d leerá el archivo y restaurará el valor anterior.
Qué hace realmente cada valor de reinicio
Docker define cuatro valores. La diferencia entre ellos solo aparece cuando la máquina se reinicia o se reinicia el daemon.
noes el valor predeterminado. El contenedor nunca se reinicia automáticamente, bajo ninguna circunstancia.alwaysreinicia el contenedor cada vez que se detiene. Si lo detuvo manualmente, volverá a iniciarse la próxima vez que se inicie el daemon de Docker. Esto suele ser inesperado: un contenedor que detuvo deliberadamente la semana pasada vuelve a ejecutarse después de un reinicio.unless-stoppedse comporta comoalways, excepto que un contenedor detenido manualmente permanece detenido después de reiniciar el daemon. Este es el valor adecuado para un servicio que detiene ocasionalmente por mantenimiento.on-failurereinicia el contenedor solo cuando termina con un código de salida distinto de cero. Puede limitar los intentos, como enrestart: on-failure:3.
Para una pila que simplemente debe estar activa cuando el servidor está activo, unless-stopped es el valor predeterminado adecuado. Elija always solo cuando quiera que un contenedor no permanezca detenido.
Por qué restart: on-failure no sobrevive a un reinicio
Muchas personas eligen on-failure porque parece una opción prudente, pero después del primer reinicio encuentran todos los contenedores detenidos. El motivo está en la definición. on-failure reacciona a una sola cosa: que el proceso del contenedor termine con un código de error.
Un reinicio no es un error. Cuando el host se apaga, systemd detiene docker.service y el daemon detiene deliberadamente cada contenedor. El contenedor no falló, por lo que la política no tiene nada a lo que reaccionar. Al volver a iniciar, el daemon comprueba qué contenedores debe reanudar, y un contenedor on-failure que se detuvo correctamente no está entre ellos. Permanece en el estado exited.
Puede comprobarlo directamente. Configure restart: on-failure en un servicio, ejecute docker compose up -d, reinicie el sistema y ejecute:
docker compose ps -aEl servicio aparece con un estado de Exited y un estado similar a Exited (0) 2 minutes ago. No hay ningún problema ni se registra ningún error. Por eso es difícil diagnosticarlo. La política hizo exactamente lo que indica.
on-failure sigue siendo útil. Es adecuada para un contenedor que ejecuta un trabajo y puede bloquearse, cuando quiere limitar el número de reintentos y evitar un bucle de reinicio. Es la herramienta incorrecta para mantener activo un servicio de ejecución prolongada después de los reinicios.
Las políticas de reinicio solo funcionan si el servicio de Docker se inicia durante el arranque
El daemon de Docker aplica las políticas de reinicio. Si el daemon no se inicia, no se aplica ninguna política. Compruébelo:
systemctl is-enabled docker
systemctl is-enabled containerdAmbos deberían mostrar enabled. Los paquetes del repositorio oficial de Docker los habilitan durante la instalación, por lo que esto suele funcionar en un servidor nuevo. Si alguno muestra disabled, corríjalo:
sudo systemctl enable --now docker containerdAquí hay un detalle importante. Ubuntu también incluye docker.socket, que inicia el daemon a petición la primera vez que algo se comunica con la API de Docker. Se puede ver que docker.socket está habilitado, asumir que el daemon está cubierto y deshabilitar docker.service para ahorrar memoria. Durante el arranque, ningún proceso llama a la API, por lo que el socket nunca recibe conexiones, el daemon nunca se inicia y ningún contenedor se pone en marcha hasta que se ejecuta el primer comando docker. La activación mediante socket no sustituye a tener docker.service habilitado.
Cuándo una unidad de systemd es la mejor opción
Las políticas de reinicio no contemplan el orden respecto al resto del sistema. El daemon se inicia y levanta los contenedores en cuanto puede. Si la pila monta mediante bind una carpeta de un volumen independiente, un recurso compartido NFS (sistema de archivos de red) o un disco cifrado, los contenedores pueden iniciarse antes de que exista esa ruta. Docker creará sin problemas una carpeta vacía en el punto de montaje e iniciará el contenedor usando esa carpeta, por lo que la base de datos se inicia sin datos.
Escriba una unidad de systemd cuando se cumpla alguna de estas condiciones. La pila necesita que un montaje, una interfaz VPN u otra unidad esté lista primero. Quiere que systemctl stop myapp y systemctl start myapp funcionen como lo hacen para cualquier otro servicio del sistema. O quiere que la pila se detenga correctamente durante el apagado, en lugar de terminar junto con el daemon. Si las unidades de systemd son nuevas para usted, cómo escribir un servicio y un temporizador de systemd explica con más detalle el formato del archivo.
Escritura de la unidad de systemd
Coloque la pila en una ruta fija fuera de un directorio personal. /srv/myapp es una buena opción, porque una unidad que se ejecuta antes de que alguien inicie sesión no necesita leer /home.
Cree /etc/systemd/system/myapp.service:
[Unit]
Description=myapp docker compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
RequiresMountsFor=/srv/myapp/data
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetHabilítela e iníciela:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.serviceUna unidad en buen estado muestra Active: active (exited). La primera vez que lo ve, puede parecer incorrecto. Es correcto: Type=oneshot con RemainAfterExit=yes significa que la unidad ejecutó su comando, el comando terminó y systemd mantiene la unidad marcada como activa para que ExecStop se ejecute durante el apagado.
Cada línea tiene una función. Requires=docker.service significa que la unidad falla rápidamente en lugar de ejecutar docker compose contra un socket inactivo. After= establece el orden, porque Requires= por sí solo no lo hace. RequiresMountsFor= hace que systemd incorpore la unidad de montaje de esa ruta y espere a que esté disponible. Esa es precisamente la razón para usar una unidad en lugar de una política de reinicio. TimeoutStartSec=0 evita que systemd cancele el trabajo de inicio mientras todavía se descarga una imagen grande.
Una nota sobre la combinación de ambos mecanismos. La documentación de Docker desaconseja combinar las políticas de reinicio con un gestor de procesos del host. Esa advertencia se refiere a un gestor de procesos que supervisa directamente el proceso del contenedor y lo reinicia mientras el daemon intenta hacer lo mismo. Una unidad de Type=oneshot no supervisa ningún proceso, por lo que es correcto mantener restart: unless-stopped en el archivo de compose junto con esta unidad, y eso es lo que necesita. systemd gestiona el orden durante el arranque y el daemon gestiona un contenedor que se detiene a las tres de la madrugada.
Verificar con un reinicio real
No hay sustituto para la prueba real. systemctl restart docker no comprueba el orden de montaje, y docker compose down seguido de docker compose up -d no comprueba nada del arranque.
sudo rebootEspere, vuelva a conectarse y compruebe en este orden:
uptime
systemctl is-active docker
docker compose psuptime confirma que está comprobando una máquina que realmente se reinició. docker compose ps, ejecutado desde el directorio de la pila, debería mostrar todos los servicios como running, con un tiempo de actividad cercano al de la máquina. Debe revisar el servicio que muestre Exited.
Si algún servicio no se inició, el registro del daemon incluye el intervalo de arranque:
journalctl -u docker.service -b --no-pager | tail -50Para una pila administrada por una unidad, journalctl -u myapp.service -b --no-pager muestra la salida exacta de docker compose durante el arranque, incluido un error al extraer una imagen o la ausencia de un archivo .env.
Elementos que interrumpen silenciosamente el inicio automático
Los contenedores creados con docker compose run nunca reciben la política de reinicio del archivo. Compose los trata como contenedores de un solo uso. Si un servicio parece ignorar su política, comprueba si se inició con run en lugar de up.
Una ruta relativa en un volumen o en una entrada de env_file se resuelve en relación con el directorio del archivo de Compose. Esto funciona desde el shell y desde una unidad que establece WorkingDirectory. Falla desde una unidad que no lo establece, porque el directorio de trabajo es /.
Docker rootless es un caso diferente. El daemon se ejecuta como un servicio de usuario, y un servicio de usuario se detiene cuando termina la última sesión de ese usuario. Habilítalo para el usuario y permite que siga ejecutándose sin ninguna sesión iniciada:
systemctl --user enable docker
sudo loginctl enable-linger $USERSin enable-linger, el daemon rootless se apaga cuando cierras la sesión y los contenedores se detienen con él. Esto parece exactamente una política de reinicio defectuosa.
Un último aspecto. Las actualizaciones de seguridad automáticas pueden reiniciar un servidor a una hora fija. Esto solo es útil si tu stack vuelve a iniciarse automáticamente. Configurarlo en una máquina nueva forma parte del trabajo inicial de la primera hora, junto con los primeros diez minutos en un VPS nuevo.
FAQ
¿Cuál es la diferencia entre restart: always y restart: unless-stopped?
Ambos reinician el contenedor cuando se detiene por sí solo. La diferencia aparece después de detener un contenedor manualmente. Con always, el contenedor vuelve a iniciarse la próxima vez que se inicia el daemon de Docker, por lo que un reinicio del sistema anula la detención manual. Con unless-stopped, el daemon recuerda que el contenedor se detuvo deliberadamente y lo deja detenido. Usa unless-stopped salvo que necesites específicamente un contenedor que permanezca detenido.
Añadí restart: unless-stopped, pero el contenedor sigue sin iniciarse después de un reinicio. ¿Por qué?
La política se aplica al contenedor, no al archivo, y editar YAML no actualiza un contenedor que ya existe. Ejecuta docker compose up -d para que Compose lo vuelva a crear y, después, compruébalo con docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' $(docker compose ps -q app). Si muestra no, el contenedor es anterior a tu cambio. Otra causa habitual es que docker.service no esté habilitado, lo que puedes comprobar con systemctl is-enabled docker.
¿Necesito una unidad de systemd si ya uso políticas de reinicio?
Normalmente no. Una política de reinicio es suficiente para una pila que solo necesita la red, como ocurre con la mayoría de las pilas. Añade una unidad cuando los contenedores dependan de algo que no esté listo cuando se inicia el daemon de Docker, como un disco externo, un volumen cifrado, un recurso compartido NFS o una interfaz VPN. La unidad permite definir el orden mediante After= y RequiresMountsFor=, algo que una política de reinicio no puede expresar.
¿Cómo detengo una pila de forma permanente para que no vuelva a iniciarse en el siguiente reinicio?
Con unless-stopped, docker compose stop es suficiente, porque un contenedor detenido manualmente no se reanuda cuando se reinicia el daemon. Con always, detenerlo no es suficiente y el contenedor vuelve a iniciarse después de un reinicio. Ejecuta docker compose down, que elimina los contenedores, o cambia primero la política con docker update --restart no my-container. Si una unidad de systemd administra la pila, ejecuta también sudo systemctl disable myapp.service; de lo contrario, la unidad volverá a iniciarla.