Docker Compose como servicio systemd: guía 2026

Correr Docker Compose como servicio systemd hace que tu stack arranque solo al bootear el servidor, se apague limpio al hacer shutdown y se levante de nuevo tras un reinicio, sin que tengas que entrar por SSH a tocar nada. Se logra con un archivo .service en /etc/systemd/system, usando Type=oneshot y RemainAfterExit=yes.

El Docker Compose servicio systemd es un archivo de unidad de systemd que ejecuta docker compose up -d al arrancar el host y docker compose down al apagarlo. systemd se encarga del ciclo de vida (inicio, parada, recarga y logs), mientras que Docker corre los contenedores y Compose define la stack. Es la forma estándar de que un proyecto Compose sobreviva reinicios en un solo servidor Linux.

En 30 segundos

  • Arranque automático: con systemctl enable tu stack se levanta sola en cada boot, sin scripts caseros ni cron.
  • La combinación clave: Type=oneshot + RemainAfterExit=yes, porque docker compose up -d termina rápido pero los contenedores siguen vivos.
  • Orden correcto: Requires=docker.service y After=docker.service para que Compose no arranque antes que el daemon de Docker.
  • Dos fuentes de logs: journalctl -u tuapp.service para el servicio y docker compose logs para la aplicación.
  • Para quién es: un solo servidor Linux con un reverse proxy, un Grafana + Prometheus, un Postgres o un staging. No es reemplazo de Kubernetes, y está bien que no lo sea.

¿Por qué no alcanza con dejar corriendo docker compose up -d?

Ponele que levantás tu stack a mano con docker compose up -d, funciona bárbaro, cerrás la terminal y te vas. Reinicia el servidor por una actualización de kernel y… ¿quién levanta los contenedores? Nadie. Ese es el agujero.

Un comando arranca la stack, pero no documenta cómo tiene que comportarse. No dice cómo iniciar en el boot, cómo frenar limpio en el shutdown, cómo recuperarse de un fallo ni dónde escribir los logs. Todo eso queda en tu cabeza (y tu cabeza no está disponible a las 3 de la mañana). Según la guía original de dev.to, la pieza que casi siempre falta no es Docker ni Compose, es la gestión del servicio. Y ahí es donde systemd hace el laburo pesado.

El reparto de tareas es prolijo: Docker corre los contenedores, Compose define la stack y systemd la prende y la apaga en el host. Cada uno hace lo suyo. Más contexto en automatizar tu pipeline de CI/CD.

¿Qué requisitos previos necesito?

Antes de escribir una sola línea del archivo de unidad, verificá que tengas esto en el servidor:

  • Docker instalado y activo: confirmalo con docker --version y systemctl status docker.
  • Docker Compose disponible: con las versiones actuales es el plugin docker compose version (sin guion). El binario viejo docker-compose también sirve, pero cambia la ruta en ExecStart.
  • Acceso root o sudo: vas a crear archivos en /etc/systemd/system/, que no es escribible por un usuario común.
  • La ruta absoluta de tu proyecto: el directorio donde vive tu docker-compose.yml, por ejemplo /opt/miapp.

Un detalle que salva dolores de cabeza: averiguá la ruta real del binario con which docker. En muchas distros es /usr/bin/docker, pero systemd no usa tu PATH, así que la ruta va completa sí o sí.

¿Cuál es la estructura del archivo de servicio systemd?

Un archivo de unidad tiene tres bloques. [Unit] describe el servicio y sus dependencias. [Service] dice cómo ejecutarlo. [Install] define cuándo tiene que arrancar. Este es un ejemplo completo y comentado:

[Unit]
Description=Mi stack Docker Compose
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/miapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose up -d
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

¿Por qué Type=oneshot y no Type=simple? Acá está el punto que rompe a mucha gente. docker compose up -d larga los contenedores en segundo plano y termina enseguida. Si usaras Type=simple, systemd vería que el proceso “murió” y marcaría el servicio como caído, aunque tus contenedores estén perfectos. Con oneshot más RemainAfterExit=yes, systemd entiende que el comando corre una vez, termina, y el servicio sigue “activo” mientras la stack esté arriba. Sobre eso hablamos en integrar con tu plataforma de CI/CD.

WorkingDirectory es obligatorio: es el equivalente a hacer cd a la carpeta antes de correr Compose, así encuentra tu docker-compose.yml. Y Requires + After garantizan el orden: primero el daemon de Docker, después tu stack.

Paso a paso: crear, habilitar y arrancar el servicio

Con el archivo listo, el flujo es corto. Cuatro comandos y quedó.

  1. Creá el archivo en /etc/systemd/system/miapp.service con sudo nano /etc/systemd/system/miapp.service y pegá el contenido de arriba.
  2. Recargá systemd con sudo systemctl daemon-reload para que lea la unidad nueva. Este paso se olvida seguido y después nada funciona.
  3. Habilitá el arranque automático con sudo systemctl enable miapp.service. Esto crea un enlace simbólico en multi-user.target.wants, que es lo que hace que arranque en cada boot.
  4. Levantá la stack ahora con sudo systemctl start miapp.service.

Ojo con la diferencia entre enable y start: enable configura el arranque en el próximo boot, start lo levanta en este momento. Querés los dos. Para chequear que quedó habilitado, tirá systemctl is-enabled miapp.service y esperás un enabled como respuesta.

¿Qué comandos systemctl necesito para gestionar la stack?

ComandoQué haceCuándo lo usás
systemctl start miappCorre docker compose up -dLevantar la stack sin reiniciar
systemctl stop miappCorre docker compose downBajar la stack limpio
systemctl restart miappdown + up seguidosAplicar cambios de config
systemctl status miappMuestra estado y últimas líneas de logVer si está viva y por qué falló
systemctl enable miappActiva arranque en el bootUna sola vez, al configurar
systemctl disable miappSaca el arranque automáticoCuando desmontás el servicio
docker compose servicio systemd diagrama explicativo

El .service del final es opcional, systemd lo asume. Detalle chico, pero se agradece cuando tipeás lo mismo cincuenta veces por día.

¿Cómo reviso los logs y resuelvo problemas?

Hay dos niveles de logs y confundirlos es fuente de frustración. Uno es el del servicio systemd (qué pasó cuando intentó arrancar). El otro es el de tu aplicación (qué dicen los contenedores). Complementá con servir contenido en múltiples idiomas.

  • Logs del servicio: journalctl -u miapp.service -f te muestra en vivo qué hizo systemd. Acá ves errores de arranque, rutas mal puestas o el daemon que no estaba listo.
  • Logs de la app: docker compose logs -f desde el directorio del proyecto, para lo que escupen tus contenedores.
  • Estado rápido: systemctl status miapp.service combina lo mejor de los dos, con las últimas líneas y el estado active (exited), que con oneshot es normal y sano, no un error.

Si ves active (exited) en verde, tranquilo: significa que el comando terminó y RemainAfterExit mantiene el servicio como activo. Es exactamente lo que buscabas.

Buenas prácticas de seguridad y backup

  • Validá antes de recargar: corré docker compose config para chequear que el YAML sea válido antes de un restart. Un typo en producción no avisa hasta que ya está roto.
  • Backup del compose: guardá una copia del docker-compose.yml y del .env antes de cada actualización. Versionalos en Git si podés.
  • Permisos ajustados: el .env con credenciales va con chmod 600. No lo dejes legible por todo el mundo.
  • Usá secrets de Compose para contraseñas en vez de meterlas como variables de entorno planas.

Si estás montando esto sobre un VPS o un servidor propio, la base importa tanto como la config. En donweb.com tenés hosting y VPS en Argentina donde correr tu stack Compose sin pelearte con la latencia hacia otro continente.

Errores comunes al configurar Docker Compose como servicio

  • Usar Type=simple: el error número uno. systemd cree que el servicio murió apenas up -d termina. Solución: Type=oneshot con RemainAfterExit=yes.
  • Olvidar daemon-reload: editás el archivo, hacés restart y systemd sigue usando la versión vieja en caché. Siempre daemon-reload después de tocar el .service.
  • Rutas relativas o binario sin ruta completa: systemd no hereda tu PATH. Poné /usr/bin/docker completo y WorkingDirectory absoluto, nunca ./.
  • Sin After=docker.service: en un reboot, tu stack intenta arrancar antes que el daemon de Docker y falla con “cannot connect to the Docker daemon”. Declará la dependencia.

Preguntas Frecuentes

¿Cómo hago que Docker Compose inicie al arrancar el servidor?

Creás un archivo .service en /etc/systemd/system/ con ExecStart=/usr/bin/docker compose up -d y corrés sudo systemctl enable tuapp.service. El comando enable crea el enlace que hace que systemd levante la stack en cada boot.

¿Cuál es la diferencia entre Type=simple y Type=oneshot?

Type=simple espera que el proceso siga vivo mientras corre el servicio, ideal para un programa en primer plano. Type=oneshot es para comandos que se ejecutan y terminan, como docker compose up -d. Para Compose usás oneshot con RemainAfterExit=yes.

¿Cómo reviso los logs del servicio en systemd?

Con journalctl -u tuapp.service -f ves los logs del servicio systemd en tiempo real. Para los logs de la aplicación en sí, usás docker compose logs -f desde el directorio del proyecto. Son dos capas distintas. Tema relacionado: ejecutar agentes completamente en local.

¿Necesito Kubernetes en lugar de esto?

No, si tenés un solo servidor Linux. Compose bajo systemd es la cantidad justa de infraestructura para herramientas internas, side projects, servicios self-hosted, staging y apps de producción chicas con límites conocidos. Kubernetes recién tiene sentido cuando necesitás varios nodos y orquestación real.

¿Por qué el estado aparece como “active (exited)”?

Es lo esperado con Type=oneshot. El comando de arranque terminó, y RemainAfterExit=yes mantiene el servicio marcado como activo aunque no haya un proceso en primer plano. Si está en verde, tu stack está corriendo bien.

Conclusión

Pasar de docker compose up -d a mano a un servicio systemd cambia una cosa concreta: tu stack deja de depender de que vos te acuerdes. Arranca sola en cada reboot, se apaga limpio y te da logs centralizados con journalctl. La receta es corta: un archivo de unidad con Type=oneshot, RemainAfterExit=yes y After=docker.service, más un enable para el boot.

Si tenés un reverse proxy, un Grafana con Prometheus o un Postgres corriendo en un solo host, hacé esto hoy. Son diez minutos de setup que te ahorran el próximo susto post-reinicio. Y acordate de los tres clásicos que rompen todo: no usar simple, no olvidar el daemon-reload y poner siempre rutas absolutas.

Fuentes

Te puede interesar...