Arranque automático de contenedores con docker-compose y systemd

Regresamos a vueltas con contenedores y docker. En este nuevo post vamos a ver como generar un servicio de systemd para nuestros ficheros docker compose o docker-compose.yml. Así que sí, después de la larga espera para hablar un poco de contenedores, vamos a encadenar un par de artículos consecutivos sobre este tema.

Iniciando tus contenedores automáticamente al inicio con docker compose y systemd

Nuestro objetivo es hacer que nuestros servicios y contenedores definidos en un fichero docker-compose.yml se inicien automáticamente con el arranque del sistema. Para ello necesitaremos:

  1. Un fichero docker-compose.yml con la definición de los servicios y contenedores.
  2. Un fichero de unidad o unit file de systemd donde se define el servicio asociado a nuestro fichero compose.
  3. Probar el inicio y parada de nuestros servicios contenerizados con systemd.
  4. Habilitar nuestro servicio para que se inicie al arranque del sistema.

Fichero docker-compose.yml base para integrar con systemd

Como parece lógico, lo primero que necesitamos para poder gestionar nuestro fichero docker compose mediante systemd es un fichero docker-compose.yml.

Como ejemplo para este artículo vamos a usar un fichero docker compose bastante simple. Solo contendrá un servicio web que será un servidor web nginx. Si sientes curiosidad por manejar ficheros docker-compose.yml más complejos puedes echarle un ojo a nuestro artículo sobre Gluetun y los ejemplos en nuestro repositorio que en él se muestran, o visitar la documentación oficial de Docker Compose.

Nuestro fichero docker-compose.yml será este:

---
services:
  web:
    image: nginx
    container_name: nginx_noroute2host
    ports:
      - "8080:80"

Y lo vamos a colocar, por ejemplo, en el siguiente directorio: /store/docker-compose-nginx. Pero tú puedes colocarlo donde prefieras. Eso sí, recuerda donde lo haces porque lo vamos a necesitar más adelante.

Unit File o fichero de unidad de systemd

Una vez que tenemos el fichero docker compose, el siguiente paso es generar un servicio de systemd asociado a nuestro fichero docker-compose.yml.

Llegados a este punto habrá alguno que se pregunte ¿qué es systemd? Aunque muchos de los que estéis leyendo esto ya lo sabréis, systemd es un gestor de sistemas y de servicios para Linux. Y esto se traduce en que es el sistema de inicialización por defecto en muchas distribuciones Linux, incluidas Debian, Redhat y sus derivadas. Así que se puede decir que hoy en día es el sistema de arranque de muchas distribuciones Linux.

Systemd funciona en base a unidades, y estas unidades se definen en ficheros de unidad. En concreto, en la ruta /etc/systemd/system/ se incluyen los ficheros de unidad personalizados del usuario entre otros.

Además, estas unidades pueden ser de diferente tipo, pero la unidad que nos interesa para llevar a cabo nuestra tareas es la de servicio o service.

Así que vamos a crear un fichero de unidad o unit file en la siguiente ruta y, por ejemplo, con el siguiente nombre: /etc/systemd/system/docker-compose-nginx.service.

[Unit]
Description=Docker compose de NGINX como servicio
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=true
User=adrimcgrady
WorkingDirectory=[TU/DIRECTORIO/QUE/CONTIENE/EL/FICHERO/DOCKER/COMPOSE]
ExecStart=[/RUTA/ABSOLUTA/A/TU/COMANDO]/docker-compose up -d --remove-orphans
ExecStop=[/RUTA/ABSOLUTA/A/TU/COMANDO]/docker-compose down

[Install]
WantedBy=multi-user.target

En el fichero de unidad de arriba hay varios parámetros que aparecen parametrizados. Para mayor claridad te dejo aquí abajo el fichero de unidad completo para nuestro ejemplo.

[Unit]
Description=Docker compose de NGINX como servicio
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=true
User=adrimcgrady
WorkingDirectory=/store/docker-compose-nginx
ExecStart=/usr/local/bin/docker-compose up -d --remove-orphans
ExecStop=/usr/local/bin/docker-compose down

[Install]
WantedBy=multi-user.target

Usando systemd para iniciar los contenedores definidos en el fichero docker-compose.yml

Antes de correr hay que aprender a andar. Pues siguiendo el símil, antes de arrancar los contenedores con el inicio del sistema hay que probar que se pueden arrancar y parar sin problemas con systemd, o más concretamente con systemctl.

Como ya tenemos definido tanto nuestro fichero docker-compose.yml como nuestro unit file de systemd, lo único que queda es probarlo.

El inicio de un servicio con systemctl es tan sencillo como:

sytemctl start NOMBRE_DE_SEVICIO

Por lo que si queremos arrancar el servicio que hemos definido como ejemplo, habrá que ejecutar simplemente:

root@raspibox:~# systemctl start docker-compose-nginx.service

Y para comprobar el estado del servicio lo podemos hacer vía systemctl status:

root@raspibox:~# systemctl status docker-compose-nginx.service
● docker-compose-nginx.service - Docker compose de NGINX como servicio
     Loaded: loaded (/etc/systemd/system/docker-compose-nginx.service; disabled; vendor preset: enabled)
     Active: active (exited) since Sun 2025-04-06 13:25:37 CEST; 1min 21s ago
    Process: 25345 ExecStart=/usr/local/bin/docker-compose up -d --remove-orphans (code=exited, status=0/SUCCES>
   Main PID: 25345 (code=exited, status=0/SUCCESS)
        CPU: 578ms

Apr 06 13:25:24 raspibox systemd[1]: Starting Docker compose de NGINX como servicio...
Apr 06 13:25:27 raspibox docker-compose[25345]:  Container nginx_noroute2host  Created
Apr 06 13:25:27 raspibox docker-compose[25345]:  Container nginx_noroute2host  Starting
Apr 06 13:25:37 raspibox docker-compose[25345]:  Container nginx_noroute2host  Started
Apr 06 13:25:37 raspibox systemd[1]: Finished Docker compose de NGINX como servicio

También podemos ver el si el contenedor está funcionando vía docker ps:

root@raspibox:~# docker ps --last 1
CONTAINER ID   IMAGE     COMMAND                  CREATED       STATUS         PORTS                                   NAMES
c6d145c6e64a   nginx     "/docker-entrypoint.…"   3 weeks ago   Up 3 minutes   0.0.0.0:8080->80/tcp, :::8080->80/tcp   nginx_noroute2host

O lo que es mejor, simplemente se puede hacer la prueba de conectar a nuestro servidor web nginx contenerizado:

Contenedor Nginx funcionando

Llegados a este punto, toca probar la parada del servicio. De manera genérica esto puede hacerse de la siguiente manera:

sytemctl stop NOMBRE_DE_SEVICIO

Por tanto, para parar el servicio de ejemplo:

root@raspibox:~# systemctl stop docker-compose-nginx.service

Y se puede comprobar que nuestro contenedor nginx_noroute2host ya no está corriendo:

root@raspibox:~# docker ps --filter name=nginx_noroute2host
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Así que si volvemos a repetir la prueba de conexión al servidor Nginx, ahora obtendremos un error:

Contenedor Nginx parado

Último paso: Habilitar el servicio al arranque

Ya está todo probado y definido así que nos queda simplemente el último paso, habilitar nuestro servicio de systemd. Esto es algo bastante sencillo y parecido a los comandos anteriores con systemctl. La sintaxis es esta:

systemctl enable NOMBRE_DE_SEVICIO

Lo que aplicado al servicio que estamos usando de ejemplo es simplemente:

systemctl enable docker-compose-nginx.service

Y ya está, ya sabes todo lo necesario para:

  • Definir tus contenedores y servicios en un fichero docker-compose.yml
  • Integrar el fichero docker-compose.yml con systemd mediante un fichero de unidad o unit file.
  • Iniciar tus contenedores del fichero compose mediante el servicio de systemd definido.
  • Habilitar este servicio al arranque del sistema.

Antes de acabar: Ficheros de ejemplo

Además de la explicación disponible en este post, se han dejado los ficheros compose y de unidad en el repositorio del blog a modo de referencia. Este es el acceso directo a cada fichero:

Enlaces de interés:

Artículo anterior

Artículos relacionados: