A logo showing the text blog.marcnuri.com
English
Inicio»Cloud Native»Docker: Configurando un contenedor como un servicio de Linux

Entradas Recientes

  • Synology DS224+: Cómo actualizar discos duros en RAID 1
  • Fabric8 Kubernetes Client 7.5 está disponible!
  • Impulsando Mi Productividad como Desarrollador con IA en 2025
  • Black Box vs White Box Testing: Cuándo Usar Cada Enfoque
  • Fabric8 Kubernetes Client 7.4.0 está disponible!

Categorías

  • Antiguo
  • Cloud Native
  • Desarrollo Backend
  • Desarrollo Frontend
  • Herramientas
  • Ingeniería de calidad
  • Inteligencia Artificial
  • JavaScript
  • Operaciones
  • Personal
  • Proyectos personales
  • Reflexiones sobre Ingeniería

Archivos

  • enero 2026
  • diciembre 2025
  • octubre 2025
  • septiembre 2025
  • julio 2025
  • mayo 2025
  • abril 2025
  • marzo 2025
  • febrero 2025
  • enero 2025
  • diciembre 2024
  • noviembre 2024
  • agosto 2024
  • junio 2024
  • mayo 2024
  • abril 2024
  • marzo 2024
  • febrero 2024
  • enero 2024
  • diciembre 2023
  • noviembre 2023
  • octubre 2023
  • septiembre 2023
  • agosto 2023
  • julio 2023
  • junio 2023
  • mayo 2023
  • abril 2023
  • marzo 2023
  • febrero 2023
  • enero 2023
  • diciembre 2022
  • noviembre 2022
  • octubre 2022
  • septiembre 2022
  • agosto 2022
  • julio 2022
  • junio 2022
  • mayo 2022
  • marzo 2022
  • febrero 2022
  • enero 2022
  • diciembre 2021
  • noviembre 2021
  • octubre 2021
  • septiembre 2021
  • agosto 2021
  • julio 2021
  • enero 2021
  • diciembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • junio 2020
  • mayo 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • noviembre 2019
  • septiembre 2019
  • julio 2019
  • diciembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • marzo 2018
  • febrero 2018
  • noviembre 2017
  • octubre 2017
  • agosto 2017
  • julio 2017
  • enero 2017
  • julio 2016
  • enero 2016
  • diciembre 2015
  • noviembre 2015
  • diciembre 2014
  • noviembre 2014
  • octubre 2014
  • marzo 2014
  • febrero 2011
  • junio 2008
  • mayo 2008
  • abril 2008
  • enero 2008
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007

Docker: Configurando un contenedor como un servicio de Linux

2018-02-23 en Cloud Native etiquetado DevOps / Docker / Linux / Portainer / Servicio / Systemctl / systemd por Marc Nuri | Última actualización: 2026-01-26
English version

Introducción

Existen múltiples formas de orquestar la gestión, iniciación, despliegue, etc. de los contenedores de Docker. Docker ofrece su propia herramienta Compose y modo Swarm. También hay herramientas de terceros para orquestar los contenedores, como Kubernetes, Rancher y Apache Mesos.

Para despliegues sencillos en un único host Linux, puede que no necesites ninguna de estas soluciones complejas. El demonio de Docker ofrece un sencillo mecanismo para arrancar, parar y consultar el estado de los contenedores desplegados en el sistema. En esta publicación veremos cómo desplegar contenedores como servicios de Linux utilizando Docker + systemd sin necesidad de utilizar herramientas de terceros.

El tutorial muestra concretamente cómo desplegar Portainer CE como un servicio de Linux.

docker as a linux systemd service
docker as a linux systemd service

Políticas de reinicio de Docker

Antes de profundizar en la integración con systemd, vale la pena mencionar que Docker tiene políticas de reinicio integradas que pueden gestionar los reinicios de contenedores automáticamente.

El flag --restart soporta varias políticas:

PolíticaDescripción
noNo reiniciar el contenedor automáticamente (por defecto)
on-failure[:max-retries]Reiniciar solo si el contenedor sale con un estado distinto de cero
alwaysSiempre reiniciar el contenedor independientemente del estado de salida
unless-stoppedComo always, pero no reinicia contenedores que fueron detenidos explícitamente

Por ejemplo, para ejecutar Portainer con reinicios automáticos:

bash
docker run -d --name portainer --restart unless-stopped \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts

Este enfoque es más simple que la integración con systemd y funciona bien para la mayoría de los casos. Sin embargo, la integración con systemd ofrece beneficios adicionales:

  • Gestión de dependencias: Iniciar contenedores después de servicios específicos (red, bases de datos, etc.)
  • Logging unificado: Los logs del contenedor aparecen en journald junto con los logs del sistema
  • Gestión consistente: Usar comandos systemctl familiares para todos los servicios
  • Orden de arranque: Control granular sobre la secuencia de inicio

Contenedor existente previamente

El método más sencillo para desplegar un contenedor como un servicio es crear un contenedor de Docker con un nombre concreto y mapear cada una de las operaciones de Docker (start y stop) con comandos de servicio de systemd.

bash
docker run -d --name portainer \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts

Una vez creado el contenedor, podemos arrancarlo, pararlo y reiniciarlo empleando comandos estándar de Docker indicando el nombre del contenedor (docker stop portainer, docker start portainer, docker restart portainer).

El siguiente paso es crear un fichero systemd unit de configuración con la descripción del servicio en el directorio /etc/systemd/system/. Para este ejemplo, crearemos un fichero con el nombre portainer.service con el siguiente contenido:

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStart=/usr/bin/docker start -a portainer
ExecStop=/usr/bin/docker stop -t 10 portainer

[Install]
WantedBy=multi-user.target

El fichero unit define un nuevo servicio y mapea los comandos docker start y docker stop a las secuencias start y stop de systemd.

Configuración explicada:

  • After=docker.service: Asegura que este servicio se inicie solo después de que Docker esté listo
  • Requires=docker.socket: Crea una dependencia fuerte hacia el socket de Docker
  • Wants=network-online.target: Espera a que la red esté disponible (dependencia suave)
  • TimeoutStartSec=0: Deshabilita el timeout de inicio (útil si la imagen necesita descargarse)
  • Restart=always: Reinicia automáticamente el contenedor si se detiene

Después de crear el fichero unit, recarga systemd para que detecte el nuevo servicio:

bash
systemctl daemon-reload

Ahora ya podremos arrancar y parar el servicio empleando los comandos correspondientes:

bash
systemctl start portainer
systemctl stop portainer

Para comprobar el estado del servicio:

bash
systemctl status portainer

Para habilitar el servicio para que se arranque automáticamente al iniciar el sistema:

bash
systemctl enable portainer.service

Crear contenedor (si aplica) al iniciar el servicio

Podemos mejorar el fichero unit anterior para que cree el contenedor si este no existe. Este enfoque permite omitir el paso de creación manual del contenedor (docker run…) y desplegar contenedores simplemente creando el fichero unit con el descriptor del servicio.

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop %n
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull portainer/portainer-ce:lts
ExecStart=/usr/bin/docker run --rm --name %n \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts
ExecStop=/usr/bin/docker stop -t 10 %n

[Install]
WantedBy=multi-user.target

Este fichero unit mejorado utiliza varias buenas prácticas:

  • Variable %n: Se expande al nombre del servicio (e.g., portainer.service), haciendo el fichero unit más portable
  • Prefijo guión (-): El - antes de los comandos ExecStartPre significa "no fallar si este comando falla". Esto es útil porque el contenedor podría no existir en la primera ejecución
  • docker pull: Asegura que siempre ejecutes la última versión de la imagen
  • Flag --rm: Elimina automáticamente el contenedor cuando se detiene, asegurando un estado limpio al reiniciar
  • Modo foreground: A diferencia del ejemplo anterior, esto ejecuta docker run directamente (no en modo detached), permitiendo a systemd monitorizar correctamente el proceso

Alternativa: Creación condicional del contenedor

Si prefieres mantener un contenedor existente en lugar de recrearlo cada vez, usa este enfoque:

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=/bin/bash -c "/usr/bin/docker container inspect portainer 2> /dev/null || /usr/bin/docker run -d --name portainer -p 9443:9443 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts"
ExecStart=/usr/bin/docker start -a portainer
ExecStop=/usr/bin/docker stop -t 10 portainer

[Install]
WantedBy=multi-user.target

Los ficheros unit tienen determinadas limitaciones cuando se configuran comandos de ejecución en las definiciones de ExecStart, ExecStop, ExecStartPre. Para evitar estas limitaciones, encapsulamos el comando dentro de un comando /bin/bash -c que nos permite más flexibilidad.

El primer comando (docker container inspect) comprueba si existe un contenedor con el mismo nombre. Si lanza un error (el contenedor no existe), se ejecuta el siguiente comando para crearlo.

Es importante ejecutar el comando docker run con el argumento -d (detached) cuando usas este enfoque. De no hacerlo, el comando no devolverá el control y el siguiente paso (ExecStart) no se ejecutará, impidiendo que systemd se active.

Dependiendo de si el contenedor existe, la secuencia de inicio se comporta diferente:

  • El contenedor ya existe:
    • ExecStartPre no devuelve error en el primer comando (docker container inspect…), por lo que no ejecuta docker run…
    • ExecStart se ejecuta normalmente y el proceso se engancha al contenedor
  • El contenedor no existe:
    • ExecStartPre devuelve un error en el primer comando, ejecutando el segundo para crear y arrancar el contenedor en modo detached
    • ExecStart ejecuta (docker start) que se engancha al contenedor ya en ejecución

Ver logs del contenedor

Un beneficio de ejecutar contenedores a través de systemd es el logging unificado. La salida del contenedor es capturada por journald, y puedes ver los logs usando el comando estándar journalctl:

bash
# Ver todos los logs del servicio
journalctl -u portainer.service

# Seguir los logs en tiempo real
journalctl -u portainer.service -f

# Ver logs desde el último arranque
journalctl -u portainer.service -b

Alternativa con Podman

Si buscas una integración más estrecha con systemd, considera Podman como alternativa a Docker. Podman es un motor de contenedores sin demonio que se integra nativamente con systemd a través de una característica llamada Quadlet.

Con Quadlet, creas ficheros declarativos simples (.container, .pod, .network) en lugar de escribir ficheros unit de systemd manualmente. Colócalos en /etc/containers/systemd/ para contenedores root o en ~/.config/containers/systemd/ para contenedores sin privilegios.

Ejemplo de fichero Quadlet para Portainer:

/etc/containers/systemd/portainer.container
[Container]
ContainerName=portainer
Image=docker.io/portainer/portainer-ce:lts
PublishPort=9443:9443
Volume=/run/podman/podman.sock:/var/run/docker.sock
Volume=portainer_data:/data

[Service]
Restart=always

[Install]
WantedBy=default.target

Después de crear este fichero, ejecuta systemctl daemon-reload y el contenedor será gestionado como un servicio nativo de systemd.

Las ventajas de Podman para la integración con systemd incluyen:

  • Arquitectura sin demonio: Sin punto único de fallo como dockerd
  • Contenedores sin root: Ejecutar contenedores sin privilegios de root
  • Integración nativa con systemd: Quadlet genera ficheros unit optimizados automáticamente
  • Soporte de auto-actualización: Descarga y reinicia automáticamente cuando hay nuevas imágenes disponibles

Conclusión

En este tutorial, he mostrado varias formas de desplegar contenedores Docker como servicios de Linux systemd:

  1. Políticas de reinicio de Docker: El enfoque más simple para gestión básica de reinicios
  2. Ficheros unit de systemd: Para gestión de dependencias, logging unificado y orden de arranque
  3. Podman con Quadlet: La solución más integrada para gestión de contenedores basada en systemd

El enfoque Docker + systemd funciona bien para:

  • Despliegues en un único host que no necesitan orquestación completa
  • Servidores donde quieres que los contenedores se gestionen junto a otros servicios systemd
  • Entornos con dependencias de servicios complejas
  • Equipos familiarizados con systemd que quieren gestión consistente en todos los servicios

Para despliegues en producción a escala, considera Kubernetes o Docker Swarm. Para escenarios más simples en un único host, los enfoques descritos en esta publicación proporcionan una alternativa ligera que aprovecha tu conocimiento existente de Linux.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Navegador de artículos
Inyección de campos "Field injection is not recommended" – Spring IOCSpring Bean Scopes: Guía para comprender los distintos scopes de un Spring Bean
© 2007 - 2026 Marc Nuri