Ejecutar Docker Compose como un servicio de Linux con systemd

Docker Compose en el arranque, gestionado por systemd.

Índice

Docker Compose en un servidor Linux debe iniciarse al arranque, detenerse correctamente al apagarse y sobrevivir a los reinicios sin intervención manual.

Docker Compose no es Kubernetes, y eso está bien para las cargas de trabajo que aborda esta guía. Para muchos sistemas reales, un proyecto de Compose en un único host Linux es la cantidad adecuada de infraestructura: simple, legible, fácil de respaldar y suficiente para herramientas internas, proyectos secundarios, servicios autoalojados, entornos de staging, aplicaciones de producción pequeñas e infraestructura de desarrollo.

docker compose config ont the table with laptop

La pieza que suele faltar es la gestión de servicios. Ejecutar esto manualmente no es suficiente:

docker compose up -d

Un solo comando inicia el stack, pero no documenta cómo debería iniciarse al arranque, detenerse durante el apagado, recargar cambios después de modificarlos, escribir registros, recuperarse de fallos o actualizarse de forma segura. Ahí es donde systemd ayuda.

Esta guía recorre cómo ejecutar un proyecto de Docker Compose como un servicio de Linux con systemd: archivos de unidad, orden de arranque, actualizaciones, registros y respaldos. La división de responsabilidades es deliberada: Docker ejecuta los contenedores, Compose define el stack y systemd inicia y detiene el proyecto en el host. Forma parte de Herramientas de desarrollo: una guía para flujos de trabajo de desarrollo.

Cuando tiene sentido ejecutar Docker Compose como un servicio

Ejecutar Compose bajo systemd tiene sentido cuando tienes:

  • Un único servidor Linux
  • Una pequeña aplicación autoalojada
  • Un stack de proxy inverso
  • Un stack de monitoreo
  • Una plataforma de desarrollo local
  • Una herramienta interna
  • Un entorno de staging
  • Un servicio de producción simple con límites conocidos

Ejemplos:

  • Nginx Proxy Manager
  • Traefik
  • Gitea
  • Grafana y Prometheus
  • PostgreSQL más una pequeña aplicación web
  • Uptime Kuma
  • Servicios de asistencia de Home Assistant
  • Registro privado
  • API interna, worker y Redis

Compose es una buena opción cuando el modelo operativo sigue siendo comprensible para una sola persona leyendo un directorio.

Cuando Docker Compose no es suficiente

Usa otra cosa cuando necesites:

  • Programación multinodo
  • Replanificación automática entre hosts
  • Descubrimiento de servicios a nivel de clúster
  • Escalado horizontal automático
  • Despliegues rodantes en muchas máquinas
  • Identidad de carga de trabajo granular
  • Políticas de red complejas
  • Operaciones de plataforma grandes para múltiples equipos

En ese punto, Kubernetes, Nomad, Swarm o una plataforma gestionada pueden ser una mejor opción.

Mi regla práctica es evitar usar Kubernetes solo para evitar aprender systemd, y evitar usar Compose cuando la carga de trabajo claramente necesita orquestación en múltiples hosts.

La arquitectura básica

Una configuración limpia separa los archivos del proyecto, la unidad de systemd y los datos persistentes en el host. El proyecto de Compose vive bajo /opt/myapp/ con compose.yaml, .env, data/, backups/ y scripts opcionales como scripts/update.sh. El archivo de unidad de systemd está en /etc/systemd/system/myapp.service.

flowchart TB subgraph host["Linux host"] systemd["systemd unit\n/etc/systemd/system/myapp.service"] compose["Docker Compose\n/opt/myapp/compose.yaml"] docker["Docker Engine"] fs["Persistent data\n/opt/myapp/data/"] end systemd -->|"ExecStart: docker compose up -d"| compose compose --> docker docker --> fs

Cada capa tiene un trabajo claro: Docker ejecuta contenedores, Compose define el stack de la aplicación, systemd inicia y detiene el proyecto de Compose en el arranque y apagado, el sistema de archivos del host almacena datos persistentes, los respaldos se mantienen explícitos y las actualizaciones pasan por pasos scripteados y revisables. Esta disposición es deliberadamente aburrida, porque la infraestructura aburrida es más fácil de reparar cuando algo se rompe a las 2 a. m.

Preparar el directorio del proyecto de Compose

Crea un directorio bajo /opt:

sudo mkdir -p /opt/myapp
sudo chown -R "$USER":"$USER" /opt/myapp
cd /opt/myapp

Crea un archivo de Compose:

nano compose.yaml

Ejemplo:

services:
  web:
    image: nginx:stable
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    healthcheck:
      test: ["CMD-SHELL", "nginx -t || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

volumes: {}

Crea el directorio de contenido:

mkdir -p html
echo "Hello from Docker Compose" > html/index.html

Prueba primero manualmente:

docker compose up -d
docker compose ps
docker compose logs --tail=50

Luego deténlo antes de entregar el ciclo de vida a systemd:

docker compose down

No crees un servicio de systemd hasta que el proyecto de Compose funcione manualmente. Mientras pruebas, mantén cerca la Hoja de trucos de Docker Compose para ps, logs, pull y estructura del proyecto.

Usar el comando moderno docker compose

Docker Engine y el plugin de Compose deben estar instalados antes de escribir un archivo de unidad. En Ubuntu, Instalar Docker en Ubuntu recorre APT, Snap, modo rootless y seguridad postinstalación para que termines con un comando docker compose funcional.

Usa esto:

docker compose version

No esto:

docker-compose version

El antiguo binario docker-compose aún existe en muchas máquinas, pero Docker moderno usa Compose como un plugin de CLI de Docker.

En archivos de servicio y scripts, prefiere:

/usr/bin/docker compose

Puedes encontrar la ruta de Docker con:

command -v docker

Normalmente es:

/usr/bin/docker

Crear un servicio de systemd para Docker Compose

Si los archivos de unidad son nuevos para ti, Ejecutar cualquier ejecutable como un servicio en Linux explica Type, ExecStart, systemctl y el flujo de trabajo general de systemd. Esta sección aplica esos patrones específicamente a un stack de Compose.

Crea el archivo de servicio:

sudo nano /etc/systemd/system/myapp.service

Usa esta unidad:

[Unit]
Description=MyApp Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Recarga systemd:

sudo systemctl daemon-reload

Inicia el servicio:

sudo systemctl start myapp.service

Habilitarlo en el arranque:

sudo systemctl enable myapp.service

Verifica el estado:

systemctl status myapp.service

Verifica contenedores:

cd /opt/myapp
docker compose ps

¿Por qué Type=oneshot y RemainAfterExit=yes?

Esta es la parte que muchas guías cometen sutilmente.

docker compose up -d inicia contenedores en modo desconectado y sale, por lo que no hay un proceso Compose de primer plano en ejecución prolongada para que systemd supervise. La unidad de systemd no debería fingir que docker compose up -d es un demonio de larga ejecución.

Usa:

Type=oneshot
RemainAfterExit=yes

Esto le dice a systemd:

  • Ejecutar el comando de inicio.
  • Considerar la unidad activa después de que el comando salga con éxito.
  • Ejecutar ExecStop cuando el servicio se detenga.

Eso coincide con el comportamiento real de Compose desconectado, por lo que Type=oneshot con RemainAfterExit=yes es el predeterminado correcto para la mayoría de los stacks.

¿Por qué no Type=simple?

Con Type=simple, systemd espera que el proceso ExecStart siga ejecutándose, pero docker compose up -d sale después de iniciar los contenedores. Eso puede hacer que systemd piense que el servicio terminó, luego llamar a la lógica de detención o marcar la unidad como inactiva dependiendo de la configuración.

Si quieres Type=simple, normalmente ejecutarías Compose en primer plano:

ExecStart=/usr/bin/docker compose up

Eso puede funcionar, pero normalmente no lo prefiero para stacks de Compose en servidores. Contenedores desconectados más ExecStop explícito son más fáciles de operar.

Una unidad más amigable para producción

Para un servidor real, prefiero una unidad ligeramente más estricta:

[Unit]
Description=MyApp Docker Compose stack
Documentation=https://example.com/docs/myapp
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
EnvironmentFile=-/opt/myapp/.env.systemd
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Detalles importantes:

  • WorkingDirectory apunta al proyecto de Compose.
  • ExecStartPre valida la configuración de Compose.
  • ExecReload recrea los servicios modificados.
  • ExecStop detiene y elimina los contenedores del proyecto de Compose y la red predeterminada.
  • EnvironmentFile=-... significa que el archivo es opcional.

Crea el archivo de entorno de systemd opcional:

nano /opt/myapp/.env.systemd

Ejemplo:

COMPOSE_PROJECT_NAME=myapp

Luego recarga systemd:

sudo systemctl daemon-reload
sudo systemctl restart myapp.service

Compose .env vs EnvironmentFile de systemd

Compose y systemd tienen sus propios mecanismos de entorno, y mezclarlos causa errores confusos de “variable no establecida” en el arranque.

Compose lee automáticamente un archivo .env en el directorio del proyecto para la sustitución de variables en el archivo de Compose.

Ejemplo .env:

APP_TAG=1.2.3
WEB_PORT=8080

Ejemplo compose.yaml:

services:
  web:
    image: nginx:${APP_TAG}
    ports:
      - "${WEB_PORT}:80"

Un EnvironmentFile de systemd establece variables de entorno para el propio comando docker compose.

Ejemplo:

EnvironmentFile=-/opt/myapp/.env.systemd

Para muchos proyectos, solo necesitas .env de Compose.

Usa un archivo de entorno de systemd cuando quieras definir cosas como:

COMPOSE_PROJECT_NAME=myapp
COMPOSE_FILE=compose.yaml
DOCKER_HOST=unix:///var/run/docker.sock

No uses ninguno de los archivos como un almacén de secretos casual. Si los secretos importan, usa secretos de Docker, un administrador de secretos externo, archivos cifrados o al menos permisos estrictos.

Establece permisos restrictivos:

chmod 600 /opt/myapp/.env
chmod 600 /opt/myapp/.env.systemd

Políticas de reinicio: Docker vs systemd

Hay dos capas de reinicio: política de reinicio de contenedores en Compose y política de reinicio de servicio de systemd, y no deben mezclarse ciegamente.

Para contenedores de larga ejecución, establece políticas de reinicio en Compose:

services:
  web:
    image: nginx:stable
    restart: unless-stopped

Valores de reinicio comunes:

Política Significado
no No reiniciar automáticamente
always Reiniciar después de la salida y reinicio del demonio
on-failure Reiniciar solo después de un fallo
unless-stopped Reiniciar a menos que se detenga manualmente

Para la mayoría de los servicios persistentes, prefiero:

restart: unless-stopped

Es predecible y respeta las detenciones manuales intencionales.

La propia unidad de systemd normalmente no debería reiniciarse repetidamente, porque docker compose up -d no es la carga de trabajo en ejecución. Los contenedores sí lo son.

Así que evita esto a menos que tengas un motivo específico:

Restart=always

En la mayoría de las unidades de Compose-como-servicio, deja que Docker maneje los reinicios de contenedores.

Verificaciones de salud

Las políticas de reinicio reinician contenedores cuando los procesos salen. No arreglan mágicamente cada aplicación no saludable.

Añade verificaciones de salud donde sean útiles:

services:
  app:
    image: example/app:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

Verifica la salud:

docker compose ps

Inspecciona un contenedor:

docker inspect container-name

Las verificaciones de salud son especialmente útiles para:

  • Aplicaciones web
  • Proxies inversos
  • Bases de datos
  • Colas
  • APIs internas
  • Workers con un endpoint de salud

Son menos útiles cuando solo verifican que un proceso existe, porque un proceso que está vivo pero atascado sigue pareciendo saludable. Una mala verificación de salud es solo otra mentira en YAML.

Orden de inicio y depends_on

Compose puede definir dependencias:

services:
  app:
    image: example/app:latest
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

Esto puede ayudar con el orden de inicio, pero no confíes demasiado en ello. Las aplicaciones aún deben manejar reintentos: las bases de datos se reinician, las redes fluctúan, DNS toma tiempo, y una aplicación resiliente reintenta las conexiones en lugar de asumir un orden de inicio perfecto.

Registros: journalctl y docker compose logs

Dos vistas de registros cubren la mayoría de la depuración: systemd captura el ciclo de vida de la unidad misma, mientras que Compose captura la salida de la aplicación de los contenedores en ejecución.

Registros del servicio systemd:

journalctl -u myapp.service -n 100 --no-pager

Seguir registros de systemd:

journalctl -u myapp.service -f

Registros del servicio Compose:

cd /opt/myapp
docker compose logs --tail=100
docker compose logs -f
docker compose logs -f web

Para la mayoría de la depuración de aplicaciones, docker compose logs es más útil; para la depuración del ciclo de vida — fallos de inicio, crashes de unidad, errores de permisos — journalctl es más útil. Si systemctl start myapp falla, revisa journalctl primero. Si el stack inicia pero la aplicación está rota, revisa docker compose logs.

Rotación de registros

Los registros de Docker pueden crecer indefinidamente si no los configuras.

Para servidores pequeños, configura la rotación de registros de Docker en /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  }
}

Reinicia Docker:

sudo systemctl restart docker

Luego reinicia el stack de Compose:

sudo systemctl restart myapp.service

Esto se aplica a contenedores recién creados. Recrea contenedores si es necesario:

cd /opt/myapp
docker compose up -d --force-recreate

La rotación de registros no es glamurosa, pero es una de las formas más fáciles de prevenir una interrupción por disco lleno en un servidor pequeño.

Actualizar un servicio de Compose

Un flujo de actualización manual simple:

cd /opt/myapp
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f

Si es gestionado por systemd, puedes usar:

sudo systemctl reload myapp.service

Si tu unidad tiene:

ExecReload=/usr/bin/docker compose up -d --remove-orphans

Pero nota: ExecReload no extrae imágenes a menos que incluyas ese paso.

Para actualizaciones explícitas, crea un script.

mkdir -p /opt/myapp/scripts
nano /opt/myapp/scripts/update.sh

Script:

#!/usr/bin/env bash
set -euo pipefail

cd /opt/myapp

docker compose config --quiet
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
docker compose ps

Hazlo ejecutable:

chmod +x /opt/myapp/scripts/update.sh

Ejecútalo:

/opt/myapp/scripts/update.sh

Entonces la unidad del servicio puede permanecer enfocada en el ciclo de vida, mientras el script de actualización maneja el despliegue.

Script de actualización más seguro con gancho de respaldo

Para servicios con estado, actualiza solo después del respaldo.

#!/usr/bin/env bash
set -euo pipefail

APP_DIR="/opt/myapp"
BACKUP_DIR="/opt/myapp/backups"

cd "$APP_DIR"

mkdir -p "$BACKUP_DIR"

echo "Validating compose file"
docker compose config --quiet

echo "Running backup hook"
if [ -x "$APP_DIR/scripts/backup.sh" ]; then
  "$APP_DIR/scripts/backup.sh"
else
  echo "No backup hook found"
fi

echo "Pulling images"
docker compose pull

echo "Recreating services"
docker compose up -d --remove-orphans

echo "Pruning unused images"
docker image prune -f

echo "Current status"
docker compose ps

Esto sigue siendo simple, pero ahora codifica un hábito operativo: respaldo antes del cambio.

Detener el servicio

Detén el stack:

sudo systemctl stop myapp.service

Eso ejecuta:

docker compose down

Por defecto, docker compose down elimina:

  • Contenedores para servicios en el archivo de Compose
  • Redes definidas por el archivo de Compose
  • La red predeterminada

No elimina volúmenes nombrados a menos que se lo pidas.

No uses casualmente:

docker compose down -v

Eso elimina volúmenes nombrados declarados en el archivo de Compose y volúmenes anónimos adjuntos a contenedores. Para bases de datos y aplicaciones con estado, eso puede significar eliminar datos reales.

Usa down -v solo cuando signifique “destruir este entorno”.

Reiniciar el servicio

Reinicia la unidad de systemd:

sudo systemctl restart myapp.service

Esto ejecuta el comando de detención y luego el comando de inicio.

Para reiniciar solo contenedores sin recrearlos:

cd /opt/myapp
docker compose restart

Distinción importante:

  • docker compose restart reinicia contenedores existentes.
  • docker compose up -d aplica cambios de configuración o imagen recreando contenedores cuando es necesario.

Si modificaste compose.yaml, usa:

docker compose up -d

No solo:

docker compose restart

Manejar contenedores huérfanos

Si renombras o eliminas un servicio en compose.yaml, los contenedores antiguos pueden permanecer como huérfanos.

Usa:

docker compose up -d --remove-orphans

Por eso los ejemplos de servicio de systemd en esta guía usan:

ExecStart=/usr/bin/docker compose up -d --remove-orphans

Mantiene el stack más cerca del archivo de Compose actual.

Respaldos

Los respaldos dependen de la carga de trabajo, pero los principios son estables.

Para montajes de enlace:

/opt/myapp/data/

Haz un respaldo de ese directorio.

Para volúmenes nombrados:

docker volume ls

Inspecciona un volumen:

docker volume inspect volume-name

Para bases de datos, las copias del sistema de archivos no siempre son suficientes. Usa respaldos conscientes de la aplicación:

Ejemplo PostgreSQL:

docker compose exec -T db pg_dump -U postgres appdb > backups/appdb.sql

Ejemplo MariaDB:

docker compose exec -T db mariadb-dump -u root -p appdb > backups/appdb.sql

Ejemplo Redis:

docker compose exec redis redis-cli BGSAVE

Un stack de Compose sin un plan de respaldo no es un servicio, es un experimento temporal que resulta tener tiempo de actividad.

Línea base de seguridad

Para un pequeño servicio de Compose en Linux, comienza con esta línea base:

  • Mantén el proyecto de Compose bajo /opt/appname.
  • Usa etiquetas de imagen explícitas, no solo latest, cuando la estabilidad importa.
  • Usa montajes de enlace o volúmenes nombrados deliberadamente.
  • No expongas puertos que no necesites.
  • Pon servicios públicos detrás de un proxy inverso.
  • Usa HTTPS en el borde.
  • Mantén los secretos fuera de Git.
  • Restringe los permisos de .env.
  • Evita contenedores privilegiados a menos que sean verdaderamente requeridos.
  • Evita montar el socket de Docker dentro de contenedores.
  • Mantén Docker e imágenes actualizados.
  • Prueba el comportamiento del firewall desde otra máquina.

Un patrón peligroso:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

Esto da al contenedor control sobre Docker. En la práctica, eso puede convertirse en control a nivel de host. Úsalo solo cuando entiendas el riesgo.

Límites de recursos

En servidores pequeños, un contenedor malo puede consumir el host.

Compose soporta configuraciones relacionadas con recursos, pero el comportamiento puede depender de la versión de Docker Engine y Compose. Para protección simple, comienza con límites a nivel de aplicación y límites de registro de Docker.

Para algunas cargas de trabajo, puedes añadir límites de memoria:

services:
  app:
    image: example/app:stable
    restart: unless-stopped
    mem_limit: 512m

También configura recuentos de workers a nivel de aplicación, límites de cola y tamaños de caché. Los límites de contenedor son útiles, pero no son un sustituto para entender la aplicación.

Ejemplo: Un servicio de Compose realista

Directorio:

/opt/whoami/
  compose.yaml
  .env

Archivo Compose:

services:
  whoami:
    image: traefik/whoami:v1.10
    restart: unless-stopped
    ports:
      - "${WHOAMI_PORT}:80"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3

Archivo .env:

WHOAMI_PORT=8080
COMPOSE_PROJECT_NAME=whoami

Unidad systemd:

[Unit]
Description=Whoami Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/whoami
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Instálalo:

sudo systemctl daemon-reload
sudo systemctl enable --now whoami.service

Prueba:

curl http://localhost:8080

Verifica el estado:

systemctl status whoami.service
cd /opt/whoami
docker compose ps

Solución de problemas

El servicio inicia pero los contenedores no están en ejecución

Verifica systemd:

journalctl -u myapp.service -n 100 --no-pager

Valida Compose:

cd /opt/myapp
docker compose config

Verifica Docker:

systemctl status docker
docker info

WorkingDirectory es incorrecto

Si systemd no puede encontrar tu archivo de Compose, confirma:

WorkingDirectory=/opt/myapp

Luego verifica:

ls -la /opt/myapp
ls -la /opt/myapp/compose.yaml

El servicio se ejecuta desde WorkingDirectory, no desde tu directorio de shell actual.

Permisos de Docker denegados

Si la unidad se ejecuta como root, normalmente puede acceder a Docker.

Si estableces User=someuser, ese usuario debe poder acceder a Docker. Normalmente eso significa pertenencia al grupo docker, o una configuración de Docker sin privilegios de root.

Verifica:

groups someuser

Añade el usuario si es apropiado:

sudo usermod -aG docker someuser

Ten cuidado. El grupo Docker es efectivamente privilegiado.

Comando Compose no encontrado

Encuentra Docker:

command -v docker

Usa la ruta completa en la unidad:

ExecStart=/usr/bin/docker compose up -d --remove-orphans

Si el plugin de Compose falta:

docker compose version

Instálalo usando tu fuente de paquetes de Docker.

Variables de entorno faltantes

Verifica la configuración de Compose como systemd la vería:

cd /opt/myapp
docker compose config

Si systemd necesita variables de entorno extra, usa:

EnvironmentFile=-/opt/myapp/.env.systemd

Si Compose necesita variables para sustitución, usa:

/opt/myapp/.env

Estos están relacionados, pero no son idénticos.

Contenedores no inician después del reinicio

Verifica si el servicio de systemd está habilitado:

systemctl is-enabled myapp.service

Habilítalo:

sudo systemctl enable myapp.service

Verifica Docker:

systemctl is-enabled docker
systemctl status docker

Verifica registros de arranque:

journalctl -u myapp.service -b --no-pager

La aplicación inicia antes de que la base de datos esté lista

Añade una verificación de salud de la base de datos y depends_on con service_healthy.

También arregla la aplicación. Debería reintentar conexiones a la base de datos. El orden de inicio de infraestructura es útil, pero la lógica de reintento de la aplicación es mejor.

Disco lleno con registros de Docker

Verifica el uso de disco de Docker:

docker system df

Verifica registros grandes de contenedores:

sudo du -h /var/lib/docker/containers | sort -h | tail

Configura la rotación de registros de Docker en /etc/docker/daemon.json.

Luego recrea contenedores.

Errores comunes

Error 1: Ejecutar docker compose up en rc.local

Ejecutar docker compose up desde rc.local o un script de inicio funciona hasta que no — usa una unidad de systemd adecuada en su lugar.

Error 2: Usar Restart=always en systemd y restart: always en Compose

Normalmente solo necesitas políticas de reinicio de contenedores en Compose. Evita que dos supervisores luchen entre sí.

Error 3: Olvidar –remove-orphans

Renombrados y eliminaciones de servicios pueden dejar contenedores antiguos atrás. Usa:

docker compose up -d --remove-orphans

Error 4: Usar docker compose restart después de cambios de configuración

restart reinicia contenedores. No aplica todos los cambios de configuración.

Usa:

docker compose up -d

Error 5: Ejecutar down -v sin pensar

Esto puede eliminar volúmenes. Para servicios con estado, eso puede significar eliminar datos.

Error 6: Sin respaldo antes de pull

Las nuevas imágenes pueden romper. Las bases de datos pueden migrar. Las etiquetas pueden moverse. Haz un respaldo primero.

Error 7: Publicar cada puerto

Solo publica lo que el host necesita exponer. El tráfico interno de servicio a servicio puede permanecer en la red de Compose.

Patrón recomendado final

Para la mayoría de los servicios Linux de un solo host, usa este patrón:

Archivo Compose:

services:
  app:
    image: example/app:stable
    restart: unless-stopped
    ports:
      - "8080:8080"
    env_file:
      - .env

Unidad systemd:

[Unit]
Description=MyApp Docker Compose stack
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStartPre=/usr/bin/docker compose config --quiet
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecReload=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
TimeoutStopSec=120

[Install]
WantedBy=multi-user.target

Habilítalo:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

Opéralo:

sudo systemctl status myapp.service
sudo systemctl restart myapp.service
journalctl -u myapp.service -f
cd /opt/myapp && docker compose logs -f

Este patrón no es sofisticado, y ese es el punto. Docker Compose es excelente para sistemas pequeños y comprensibles, systemd es excelente para iniciar y detener servicios del host, y juntos te dan un modelo de despliegue de servidor único confiable sin fingir que cada proyecto necesita un clúster. Para comandos a nivel de contenedor fuera de Compose — imágenes, volúmenes, redes y limpieza — consulta la Hoja de trucos de Docker.

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.