Qué vas a conseguir
Un servidor Linux con Docker donde las herramientas internas (un panel, un Grafana, un runner de Jenkins) corren como contenedores: se arrancan con un fichero, se actualizan cambiando una versión y sus datos sobreviven a los reinicios. Y la rutina de diagnóstico cuando uno falla.
Antes de empezar
- Un servidor Ubuntu o RHEL ya configurado (guías del Nivel 2), con 20 GB libres en
/var/lib/docker. - Saber qué puertos va a exponer cada herramienta: el firewall del servidor tiene que dejarlos pasar.
- Un lugar donde guardar los ficheros Compose bajo Git.
1. Instalar y comprobar
curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh...sudo usermod -aG docker ansible && newgrp dockerdocker version --format '{{.Server.Version}}'27.3.1docker run --rm hello-world | head -3Hello from Docker!This message shows that your installation appears to be working correctly.docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 1 0 13.3kB 13.3kB (100%)Containers 0 0 0B 0BLocal Volumes 0 0 0B 0BEl usuario en el grupo docker equivale a root en ese servidor: solo quien administra la máquina debe estar en él.
2. La primera herramienta con Compose
services: grafana: image: grafana/grafana-oss:11.3.0 container_name: grafana restart: unless-stopped ports: - "3000:3000" environment: GF_SERVER_ROOT_URL: https://tools.contoso.local/grafana/ GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin volumes: - grafana-data:/var/lib/grafana secrets: - grafana_admin healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:3000/api/health || exit 1"] interval: 30s timeout: 5s retries: 3 volumes: grafana-data: secrets: grafana_admin: file: ./secrets/grafana_admin.txtTres decisiones que te ahorran problemas: la versión fija de la imagen (nunca latest en un servidor), los datos en un volumen con nombre (no dentro del contenedor) y la contraseña en un secreto desde fichero, que va al .gitignore.
mkdir -p ~/stacks/grafana/secrets && cd ~/stacks/grafanaopenssl rand -base64 24 > secrets/grafana_admin.txt && chmod 600 secrets/grafana_admin.txtecho "secrets/" >> .gitignoredocker compose up -d[+] Running 2/2 ✔ Volume "grafana_grafana-data" Created ✔ Container grafana Starteddocker compose psNAME IMAGE STATUS PORTSgrafana grafana/grafana-oss:11.3.0 Up 40 seconds (healthy) 0.0.0.0:3000->3000/tcpsudo firewall-cmd --add-port=3000/tcp --permanent && sudo firewall-cmd --reload3. Cuando un contenedor se cae
docker compose psNAME IMAGE STATUSgrafana grafana/grafana-oss:11.3.0 Restarting (1) 12 seconds agodocker compose logs --tail 30 grafanagrafana | Error: ✗ failed to connect to database: open /var/lib/grafana/grafana.db: permission denieddocker inspect grafana --format '{{.State.ExitCode}} {{.State.Error}} {{.RestartCount}}'1 7docker run --rm -v grafana_grafana-data:/data alpine ls -ln /data | head -3total 1032-rw-r----- 1 0 0 1056768 Oct 22 07:40 grafana.dbdocker run --rm -v grafana_grafana-data:/data alpine chown -R 472:0 /datadocker compose restart grafana && sleep 20 && docker compose ps --format '{{.Name}} {{.Status}}'grafana Up 20 seconds (healthy)El orden siempre es el mismo: ps (qué estado), logs --tail (qué dice), inspect (código de salida y reinicios). Un contenedor en Restarting con el mismo error en bucle no se arregla reiniciando: se arregla leyendo el log. Aquí el volumen había sido restaurado con root y Grafana corre como el usuario 472.
Si el problema es de red, docker exec -it grafana sh te mete dentro para hacer wget a lo que el contenedor no alcanza, y docker network inspect bridge enseña qué IP tiene cada uno.
4. Actualizar sin perder datos
docker run --rm -v grafana_grafana-data:/data -v $PWD/backup:/backup alpine tar czf /backup/grafana-data-2026-10-22.tgz -C /data .sed -i 's/grafana-oss:11.3.0/grafana-oss:11.3.1/' docker-compose.ymldocker compose pull && docker compose up -d[+] Running 1/1 ✔ Container grafana Starteddocker compose logs --tail 3 grafana | grep -i "http server listen"grafana | logger=http.server t=2026-10-22T08:01:12Z level=info msg="HTTP Server Listen" address=[::]:3000Copia del volumen, cambio de versión en el fichero, pull, up -d. Si la nueva versión falla, vuelves a la anterior con el mismo sed y, si hace falta, restauras el .tgz. El fichero Compose con el cambio de versión es el commit que va al ticket.
5. Limpieza mensual
docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 9 3 4.1GB 2.7GB (65%)Build Cache 41 0 1.2GB 1.2GBdocker image prune -a --filter "until=720h"docker builder prune -fdocker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 3 3 1.4GB 0B (0%)Nunca ejecutes docker volume prune en un servidor sin revisar antes docker volume ls: borra los volúmenes que ningún contenedor usa en ese momento, incluidos los de un servicio parado a propósito.
Siguientes pasos
- Pon un proxy inverso (Traefik o Caddy) delante de los contenedores para que todos salgan por 443 con certificado y sin abrir un puerto por herramienta.
- Mete
docker compose pull && up -den el playbook de Ansible: la actualización de las herramientas pasa a ser una tarea más del parcheo. - Con más de un servidor o con alta disponibilidad, el salto es Kubernetes (Nivel 4); hasta entonces, Compose en un servidor bien respaldado es suficiente para las herramientas internas.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →