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 enabletu stack se levanta sola en cada boot, sin scripts caseros ni cron. - La combinación clave:
Type=oneshot+RemainAfterExit=yes, porquedocker compose up -dtermina rápido pero los contenedores siguen vivos. - Orden correcto:
Requires=docker.serviceyAfter=docker.servicepara que Compose no arranque antes que el daemon de Docker. - Dos fuentes de logs:
journalctl -u tuapp.servicepara el servicio ydocker compose logspara 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 --versionysystemctl status docker. - Docker Compose disponible: con las versiones actuales es el plugin
docker compose version(sin guion). El binario viejodocker-composetambién sirve, pero cambia la ruta enExecStart. - 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ó.
- Creá el archivo en
/etc/systemd/system/miapp.serviceconsudo nano /etc/systemd/system/miapp.servicey pegá el contenido de arriba. - Recargá systemd con
sudo systemctl daemon-reloadpara que lea la unidad nueva. Este paso se olvida seguido y después nada funciona. - Habilitá el arranque automático con
sudo systemctl enable miapp.service. Esto crea un enlace simbólico enmulti-user.target.wants, que es lo que hace que arranque en cada boot. - 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?
| Comando | Qué hace | Cuándo lo usás |
|---|---|---|
systemctl start miapp | Corre docker compose up -d | Levantar la stack sin reiniciar |
systemctl stop miapp | Corre docker compose down | Bajar la stack limpio |
systemctl restart miapp | down + up seguidos | Aplicar cambios de config |
systemctl status miapp | Muestra estado y últimas líneas de log | Ver si está viva y por qué falló |
systemctl enable miapp | Activa arranque en el boot | Una sola vez, al configurar |
systemctl disable miapp | Saca el arranque automático | Cuando desmontás el servicio |

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 -fte 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 -fdesde el directorio del proyecto, para lo que escupen tus contenedores. - Estado rápido:
systemctl status miapp.servicecombina lo mejor de los dos, con las últimas líneas y el estadoactive (exited), que cononeshotes 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 configpara chequear que el YAML sea válido antes de unrestart. Un typo en producción no avisa hasta que ya está roto. - Backup del compose: guardá una copia del
docker-compose.ymly del.envantes de cada actualización. Versionalos en Git si podés. - Permisos ajustados: el
.envcon credenciales va conchmod 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 -dtermina. Solución:Type=oneshotconRemainAfterExit=yes. - Olvidar daemon-reload: editás el archivo, hacés
restarty systemd sigue usando la versión vieja en caché. Siempredaemon-reloaddespués de tocar el.service. - Rutas relativas o binario sin ruta completa: systemd no hereda tu
PATH. Poné/usr/bin/dockercompleto yWorkingDirectoryabsoluto, 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.






