EbenezerIT · Services
Inicio/Guías/Guía 03
GUÍA 03 · EPISODIOS 8 Y 9 Descargar PDF Vídeo en YouTube · próximamente

Docker, Docker Compose y Nginx como proxy inverso

Instala Docker desde su repositorio oficial, entiende cómo funcionan imágenes, contenedores y volúmenes, evita la trampa de Docker con el cortafuegos y publica tu primera aplicación en su propio subdominio con HTTPS.

~2,5 horas
la primera vez, sin prisa
Requisito
Guías 01 y 02 terminadas
Ubuntu 24.04 LTS
Docker CE · Compose v2 · Nginx
Pasos
17
i

Cómo usar esta guía

Aquí tienes todos los pasos y comandos, en orden, con el botón Copiar en cada bloque. La explicación de cada paso (el porqué de cada línea) está en el vídeo del episodio. Los valores resaltados en TU_... son los que tienes que cambiar por los tuyos.

ANTES DE EMPEZAR

Qué vas a construir

Al terminar tendrás Docker instalado y una aplicación real, IT-Tools (una caja de herramientas web para técnicos: generador de contraseñas, conversor de formatos, calculadora de subredes...), funcionando en un contenedor y publicada en https://herramientas.TU_DOMINIO a través del Nginx de la Guía 02.

Es el mismo esquema que usarás en el resto del curso para WordPress, Uptime Kuma, Grafana o la VPN:

PiezaPapel
ContenedorLa aplicación, aislada, escuchando solo dentro del servidor (en 127.0.0.1). Desde Internet no se puede llegar a ella directamente.
NginxLa única puerta de entrada (puertos 80 y 443). Pone el HTTPS y reenvía cada subdominio al contenedor que le toca.
Docker ComposeUn archivo de texto (compose.yaml) que describe cada aplicación: qué imagen, qué puertos, qué datos guardar. Con él, montar o rehacer una aplicación es un solo comando.

El recorrido de una visita

  1. El navegador pide https://herramientas.TU_DOMINIO. El DNS responde TU_IP.
  2. Nginx recibe la conexión en el 443, presenta el certificado de ese subdominio y descifra la petición.
  3. Nginx la reenvía por dentro del servidor a http://127.0.0.1:8081, donde escucha el contenedor.
  4. El contenedor responde a Nginx, y Nginx se lo devuelve cifrado al navegador.

Valores de esta guía

ValorQué es
TU_DOMINIOTu dominio, como en las guías anteriores
TU_IPLa IP del VPS
TU_USUARIOTu usuario del servidor
TU_VERSIONLa versión exacta de la imagen de IT-Tools (la buscarás en el paso 11)
i

Recordatorio

  • Haz una instantánea en IONOS antes de empezar (02-nginx-https).
  • Bloques verdes en el servidor (ssh lab), azules en PowerShell de tu PC, morados son contenido de archivos.
  • sudo nginx -t siempre antes de recargar Nginx.

Parte A

Docker

Instalamos Docker como recomienda el propio proyecto, entendemos sus piezas y dejamos resuelto desde el principio el fallo de seguridad más típico de los servidores con Docker. Corresponde al episodio 8.

Paso 01

Qué es Docker y por qué lo usamos

Muchas aplicaciones necesitan versiones concretas de un lenguaje, librerías y ajustes que chocan entre sí.

ConceptoQué esComparación
ImagenUna plantilla de solo lectura con la aplicación y todo lo que necesita. La publica el autor (en Docker Hub, normalmente)El instalador de un programa
ContenedorUna imagen en marcha. Puedes tener varios contenedores de la misma imagenEl programa abierto
VolumenUna carpeta que sobrevive al contenedor. Ahí van los datos (bases de datos, archivos subidos)Tu carpeta de Documentos: si reinstalas el programa, no se borra
Puerto publicadoConecta un puerto del servidor con un puerto del contenedorUn tubo entre la calle y una habitación
Red de DockerUna red privada interna por la que los contenedores hablan entre síLa red de la oficina
ComposeUn archivo que describe uno o varios contenedores y cómo se conectanLa receta
i

Contenedor no es lo mismo que máquina virtual

Una máquina virtual lleva un sistema operativo completo dentro. Un contenedor comparte el núcleo (kernel) de Ubuntu y solo aísla la aplicación: arranca en segundos y gasta muy poca memoria. A cambio, el aislamiento es menor que el de una máquina virtual, y por eso importa cómo lo configures (pasos 5 y 7).

✓

Comprueba antes de seguir

Sabes explicar con tus palabras la diferencia entre imagen, contenedor y volumen. Lo vas a necesitar en el vídeo.

Paso 02

Quitar versiones antiguas o no oficiales

Ubuntu tiene sus propios paquetes de Docker (docker.io), pero van con retraso y no incluyen Docker Compose v2.

En el servidor
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
    sudo apt-get remove -y $pkg
done
✓

Comprueba antes de seguir

Termina sin errores. Los avisos de «not installed» son normales.

Paso 03

Añadir el repositorio oficial de Docker

Vamos a instalar Docker desde los servidores de Docker, no desde los de Ubuntu.

En el servidor
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Ahora le decimos a apt dónde está el repositorio. Es un solo comando, partido en líneas con \ al final para que se lea mejor:

En el servidor
echo deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
cat /etc/apt/sources.list.d/docker.list
Salida esperada
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable
✓

Comprueba antes de seguir

El archivo contiene arch=amd64 y noble. Si ves arch= o un hueco vacío donde debería ir noble, el comando se partió mal al copiarlo: bórralo con sudo rm /etc/apt/sources.list.d/docker.list y repítelo.

Paso 04

Instalar Docker y probarlo

Con el repositorio añadido, apt ya puede instalar Docker y lo mantendrá actualizado como cualquier otro paquete.

En el servidor
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Comprueba que el servicio funciona y arranca solo, y haz la prueba clásica:

En el servidor
systemctl is-enabled docker containerd
sudo docker run --rm hello-world
docker compose version
Salida esperada
enabled
enabled
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
Docker Compose version v2.x.x
✓

Comprueba antes de seguir

Ves Hello from Docker! y la versión de Compose empieza por v2. Si docker compose version dice command not found, falta el paquete docker-compose-plugin.

Paso 05

Usar Docker sin sudo (y lo que implica)

Escribir sudo delante de cada comando de Docker es incómodo.

!

Estar en el grupo docker equivale a ser root

Quien puede dar órdenes a Docker puede arrancar un contenedor que monte / (todo el disco del servidor) y modificar cualquier archivo, sin que le pidan contraseña. Por tanto, meter a un usuario en el grupo docker es darle permisos de administrador sin sudo.

En tu servidor eso es aceptable porque tu usuario ya es administrador y solo se puede entrar con tu clave SSH (Guía 01). Lo que nunca debes hacer es meter en el grupo docker a un usuario que no deba ser administrador.

En el servidor
sudo usermod -aG docker TU_USUARIO
exit

Vuelve a entrar con ssh lab y prueba sin sudo:

En el servidor
groups
docker ps
Salida esperada
TU_USUARIO sudo users docker
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
✓

Comprueba antes de seguir

groups incluye docker y docker ps responde con la cabecera de la tabla (vacía) en vez de permission denied.

Paso 06

Limitar el tamaño de los registros

Por defecto, Docker guarda todo lo que escribe cada contenedor en un registro que crece sin límite.

En el servidor
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF
sudo systemctl restart docker
docker info --format '{{.LoggingDriver}}'
i

Solo afecta a los contenedores nuevos

Los contenedores que ya existían conservan su configuración de registros hasta que se vuelven a crear (docker compose up -d después de un cambio, o docker compose down y up -d). En un servidor recién instalado no hay ninguno, así que no te afecta.

!

Si Docker no arranca después de esto

Casi seguro es un error de sintaxis en el JSON. Mira el motivo con sudo journalctl -u docker -n 20 --no-pager, corrige el archivo y vuelve a reiniciar.

✓

Comprueba antes de seguir

docker info --format ... responde json-file y systemctl is-active docker dice active.

Paso 07

La trampa de Docker y el cortafuegos

Este es el error de seguridad más común en servidores con Docker, y conviene entenderlo bien.

Por qué pasa

UFW y Docker escriben reglas en el mismo cortafuegos del núcleo (iptables/nftables), pero en sitios distintos. El tráfico hacia un contenedor se desvía (se «reenvía») antes de llegar a las reglas de UFW, así que UFW nunca llega a verlo.

Compruébalo tú mismo (y por qué hoy no te pasa nada)

En el servidor
docker run -d --name prueba -p 8080:80 nginx:alpine
En el servidor
sudo ufw status | grep 8080 || echo "UFW no tiene ninguna regla para el 8080"
sudo ss -tlpn | grep 8080

UFW no tiene regla para el 8080, y sin embargo ss muestra que algo escucha en 0.0.0.0:8080. Ahora, desde tu PC:

En tu PC · PowerShell
curl.exe -I --max-time 5 http://TU_IP:8080

Fallará por tiempo de espera. No es UFW quien te protege: es el firewall de IONOS, que solo deja pasar 22, 80 y 443. Esta es exactamente la razón de tener dos capas (Guía 01, paso 16). Si un día abres por error el 8080 en el panel de IONOS, ese contenedor queda expuesto aunque UFW diga que no.

Borra el contenedor de prueba:

En el servidor
docker rm -f prueba

La regla del curso

■

Publica los puertos siempre en 127.0.0.1

En todos los contenedores del curso escribiremos el puerto así: 127.0.0.1:8081:80. La primera parte es la dirección del servidor donde escucha. 127.0.0.1 es la dirección interna, la que solo es accesible desde el propio servidor. Así el contenedor no es accesible desde fuera de ninguna manera, pase lo que pase con los cortafuegos, y la única forma de llegar a él es a través de Nginx, que pone el HTTPS y los controles.

Cómo se escribeEscucha enAccesible desde Internet
-p 8080:800.0.0.0:8080 (todas)Sí, si el firewall de IONOS lo permite. UFW no lo frena
-p 127.0.0.1:8080:80127.0.0.1:8080No. Solo desde el propio servidor (Nginx)
i

La excepción: servicios que no son web

Algunos servicios no pueden ir detrás de Nginx porque no son HTTP, como la VPN WireGuard (UDP 51820). Esos se publican en todas las direcciones a propósito, y se abren de forma consciente en IONOS. Lo veremos en su episodio.

✓

Comprueba antes de seguir

docker ps -a ya no muestra el contenedor prueba, y sabes explicar por qué UFW no protege los puertos de Docker.

Paso 08

Los comandos que vas a usar a diario

Con estos comandos manejarás el 95 % de tu día a día con Docker.

En el servidor
docker run -d --name demo nginx:alpine
docker ps
docker logs demo
docker exec -it demo sh

El último comando te mete dentro del contenedor: el cursor cambia a / #. Escribe ls /usr/share/nginx/html para ver sus archivos y exit para salir. Después:

En el servidor
docker stop demo
docker ps
docker ps -a
docker rm demo
docker images
docker image prune -a
ComandoQué hace
docker psLista los contenedores en marcha
docker ps -aLista todos, también los parados
docker logs NOMBRELo que ha escrito el contenedor. Con -f lo sigue en directo; con --tail 50, solo las últimas 50 líneas
docker exec -it NOMBRE shAbre una terminal dentro del contenedor (-i interactivo, -t con terminal). Algunas imágenes tienen bash; las más ligeras, solo sh
docker stop / start / restart NOMBREPara, arranca o reinicia un contenedor
docker rm NOMBREBorra un contenedor parado (-f lo para y lo borra a la vez)
docker imagesLas imágenes descargadas y lo que ocupan
docker image prune -aBorra las imágenes que no usa ningún contenedor. Te pide confirmación: responde y
docker system dfCuánto disco ocupa Docker en total (imágenes, contenedores, volúmenes)
✓

Comprueba antes de seguir

docker ps -a está vacío y docker images ya no muestra nginx:alpine ni hello-world.

Parte B

Docker Compose

En vez de recordar comandos docker run larguísimos, cada aplicación se describe en un archivo. Es más claro, se puede guardar en Git y rehacer la aplicación es un solo comando. Sigue siendo el episodio 8.

Paso 09

Dónde viven tus aplicaciones

Es importante tener un orden fijo: cada aplicación en su carpeta, con su compose.yaml y sus datos al lado.

En el servidor
sudo mkdir -p /opt/apps
sudo chown TU_USUARIO:TU_USUARIO /opt/apps
mkdir -p /opt/apps/it-tools
cd /opt/apps/it-tools
pwd

La estructura que seguiremos en todo el curso:

Salida esperada · estructura
/opt/apps/
├── it-tools/
│   └── compose.yaml
├── wordpress/          (episodio 10)
│   ├── compose.yaml
│   ├── .env            (contraseñas)
│   └── datos/          (los volúmenes)
└── uptime-kuma/        (episodio 23)
    ├── compose.yaml
    └── datos/
✓

Comprueba antes de seguir

pwd responde /opt/apps/it-tools.

Paso 10

Tu primer compose.yaml

Describimos IT-Tools en un archivo.

En el servidor
tee /opt/apps/it-tools/compose.yaml > /dev/null <<'EOF'
services:
  it-tools:
    image: corentinth/it-tools:latest
    container_name: it-tools
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:80"
EOF
cat /opt/apps/it-tools/compose.yaml
!

En YAML, los espacios importan

La sangría (los espacios al principio de cada línea) indica qué va dentro de qué. Usa siempre espacios, nunca tabuladores, y de dos en dos. Si un nivel está mal alineado, Compose da un error como yaml: line 5: mapping values are not allowed.

i

¿Y la línea version: '3.8' que sale en muchos tutoriales?

Ya no se usa. Compose v2 la ignora y avisa con the attribute version is obsolete. Si la ves en un tutorial, bórrala.

Arranca la aplicación:

En el servidor
cd /opt/apps/it-tools
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8081
Salida esperada
NAME       IMAGE                        STATUS         PORTS
it-tools   corentinth/it-tools:latest   Up 5 seconds   127.0.0.1:8081->80/tcp

HTTP/1.1 200 OK
✓

Comprueba antes de seguir

  • STATUS dice Up y PORTS empieza por 127.0.0.1:8081.
  • curl responde 200 OK.
  • Desde tu PC, curl.exe -I --max-time 5 http://TU_IP:8081 falla: la aplicación no es accesible desde fuera. Correcto.
Paso 11

Fijar la versión de la imagen

Con latest, cada vez que actualices descargas «lo último», sea lo que sea.

11.1 · Busca la versión

  1. Abre en el navegador hub.docker.com/r/corentinth/it-tools/tags.
  2. Verás una lista de etiquetas. Ignora latest y fíjate en la más reciente con número, del estilo 2024.10.22-7ca5933 (fecha y un código).
  3. Apúntala en la tabla como TU_VERSION.
★

En otras imágenes, el mismo método

Cada proyecto numera sus versiones a su manera (1.23.4, v2, 2024.10.22...). Busca siempre en la pestaña Tags de Docker Hub o en la página de Releases del proyecto en GitHub, y elige una etiqueta concreta, no latest ni solo el número principal (2).

11.2 · Cámbiala en el archivo

En el servidor
cd /opt/apps/it-tools
sed -i 's|corentinth/it-tools:latest|corentinth/it-tools:TU_VERSION|' compose.yaml
grep image compose.yaml
docker compose up -d
docker compose ps
✓

Comprueba antes de seguir

docker compose ps muestra la imagen con tu versión y el estado Up.

Parte C

Nginx como proxy inverso

La aplicación ya funciona, pero solo dentro del servidor. Ahora Nginx la publica en su propio subdominio con HTTPS. Este mismo esquema lo repetirás con cada aplicación del curso. Corresponde al episodio 9.

Paso 12

Qué es un proxy inverso

Un proxy inverso es un intermediario que recibe las peticiones de Internet y las reenvía a la aplicación que toca.

VentajaPor qué importa
Un solo punto de entradaSolo los puertos 80 y 443 abiertos, para todas las aplicaciones. Cada una se distingue por su subdominio
HTTPS en un único sitioLos certificados y el cifrado los gestiona Nginx. Las aplicaciones no tienen que saber nada de HTTPS
Las aplicaciones quedan ocultasEscuchan solo en 127.0.0.1. Si una tiene un fallo de seguridad, no es accesible directamente
Control centralizadoRestringir por IP, limitar peticiones o añadir cabeceras se hace en Nginx, igual para todas
i

Proxy «inverso» frente a proxy normal

Un proxy normal está del lado del cliente (por ejemplo, el de una empresa que filtra la navegación de sus empleados). El inverso está del lado del servidor y representa a las aplicaciones ante el mundo.

✓

Comprueba antes de seguir

Sabes explicar por qué el contenedor escucha en 127.0.0.1 y quién pone el HTTPS.

Paso 13

Ajustes comunes de proxy

Cuando Nginx reenvía una petición, la aplicación ve que viene de 127.0.0.1 (de Nginx), no del visitante real, y cree que la conexión es HTTP aunque sea HTTPS.

13.1 · Soporte de WebSocket

Muchas aplicaciones modernas (Uptime Kuma, Grafana, chats...) mantienen una conexión abierta para actualizar la pantalla en tiempo real. Eso se llama WebSocket y necesita una pequeña regla global:

En el servidor
sudo tee /etc/nginx/conf.d/websocket.conf > /dev/null <<'EOF'
# Ebenezer: soporte de WebSocket para las aplicaciones detrás del proxy
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
EOF

13.2 · El snippet de proxy

En el servidor
sudo tee /etc/nginx/snippets/proxy.conf > /dev/null <<'EOF'
# Ebenezer: ajustes comunes para reenviar a una aplicación
proxy_http_version 1.1;
proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade           $http_upgrade;
proxy_set_header Connection        $connection_upgrade;
proxy_read_timeout 60s;
EOF
sudo nginx -t
✓

Comprueba antes de seguir

sudo nginx -t dice syntax is ok. Todavía no lo usa ningún sitio.

Paso 14

Subdominio y certificado

Cada aplicación tendrá su subdominio.

14.1 · Registro DNS

En el panel DNS de IONOS, añade un registro A: nombre de host herramientas, apunta a TU_IP, TTL 1 hora. Comprueba desde tu PC a los pocos minutos:

En tu PC · PowerShell
Resolve-DnsName herramientas.TU_DOMINIO -Server 1.1.1.1

14.2 · Sitio HTTP mínimo

En el servidor
sudo tee /etc/nginx/sites-available/herramientas.TU_DOMINIO > /dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name herramientas.TU_DOMINIO;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
EOF
sudo ln -s /etc/nginx/sites-available/herramientas.TU_DOMINIO /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

14.3 · El certificado

En el servidor
sudo certbot certonly --webroot -w /var/www/letsencrypt -d herramientas.TU_DOMINIO --dry-run
sudo certbot certonly --webroot -w /var/www/letsencrypt -d herramientas.TU_DOMINIO --cert-name herramientas.TU_DOMINIO

Primero el ensayo y, si sale The dry run was successful, el de verdad.

i

¿Un certificado por subdominio o uno para todos?

Podrías añadir herramientas al certificado del dominio principal (Anexo C de la Guía 02). Preferimos uno por aplicación: si un día quitas una aplicación, borras su certificado sin tocar los demás, y si falla la renovación de uno, no afecta al resto. El script que recarga Nginx (Guía 02, paso 14) sirve para todos.

✓

Comprueba antes de seguir

sudo certbot certificates muestra dos certificados: TU_DOMINIO y herramientas.TU_DOMINIO.

Paso 15

Publicar la aplicación con HTTPS

Ahora el sitio completo: el bloque HTTP del paso anterior más un bloque HTTPS que, en vez de servir archivos de una carpeta, reenvía todo a la aplicación.

En el servidor
sudo tee /etc/nginx/sites-available/herramientas.TU_DOMINIO > /dev/null <<'EOF'
# HTTP: Let's Encrypt y redirección a HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name herramientas.TU_DOMINIO;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS: reenvío a IT-Tools (contenedor en 127.0.0.1:8081)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name herramientas.TU_DOMINIO;

    ssl_certificate     /etc/letsencrypt/live/herramientas.TU_DOMINIO/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/herramientas.TU_DOMINIO/privkey.pem;
    include snippets/tls.conf;

    add_header Strict-Transport-Security "max-age=86400" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location / {
        proxy_pass http://127.0.0.1:8081;
        include snippets/proxy.conf;
    }

    access_log /var/log/nginx/herramientas.access.log;
    error_log  /var/log/nginx/herramientas.error.log;
}
EOF
sudo nginx -t && sudo systemctl reload nginx

Pruébalo desde tu PC:

En tu PC · PowerShell
curl.exe -I http://herramientas.TU_DOMINIO
curl.exe -I https://herramientas.TU_DOMINIO
✓

Comprueba antes de seguir

  • HTTP da 301 hacia https://herramientas.TU_DOMINIO/.
  • HTTPS da 200.
  • En el navegador, https://herramientas.TU_DOMINIO abre IT-Tools con el candado. Prueba el «Token generator» o el «IPv4 subnet calculator»: funcionan dentro de tu servidor.
i

Esta aplicación la puede usar cualquiera

IT-Tools no tiene usuarios ni guarda datos, así que publicarla no es un riesgo. Las aplicaciones de administración (paneles, Grafana, Uptime Kuma...) no se publican así a todo el mundo: en el episodio 12 aprenderás a dejarlas accesibles solo a través de la VPN, añadiendo dos líneas a este mismo bloque.

Paso 16

Actualizar un contenedor

Las aplicaciones en contenedores no se actualizan con las actualizaciones automáticas de Ubuntu: lo haces tú, cuando decidas.

  1. Mira en Docker Hub (o en las Releases de GitHub) si hay una versión nueva y lee qué cambia, sobre todo si avisa de cambios incompatibles (breaking changes).
  2. En aplicaciones con datos, haz antes una copia de seguridad o una instantánea.
  3. Cambia la versión en compose.yaml y aplica:
En el servidor
cd /opt/apps/it-tools
sed -i 's|it-tools:VERSION_ANTERIOR|it-tools:VERSION_NUEVA|' compose.yaml
docker compose pull
docker compose up -d
docker compose ps
docker image prune -f
★

Si la versión nueva falla

Vuelve a poner la versión anterior en compose.yaml y repite docker compose up -d. Si ya borraste la imagen vieja, Docker la descargará otra vez. Por esto latest es mala idea: con latest no sabrías ni a qué versión volver.

!

needrestart y Docker

Cuando actualices Ubuntu y salga la pantalla morada de needrestart, no marques el servicio docker: reiniciarlo para y arranca todos los contenedores. Las actualizaciones del propio Docker (paquetes docker-ce, containerd.io) ya reinician lo necesario por su cuenta.

✓

Comprueba antes de seguir

Sabes cómo actualizar y cómo volver atrás. Con IT-Tools en su última versión no hace falta hacerlo ahora.

Paso 17

Comprobación final e instantánea

Revisamos que todo está como debe, que sobrevive a un reinicio y guardamos el punto de partida de la siguiente guía.

En el servidor
systemctl is-enabled docker containerd
docker compose -f /opt/apps/it-tools/compose.yaml ps
sudo ss -tlpn | grep -E ':(80|443|8081) '
sudo nginx -t
sudo certbot renew --dry-run
ComprobaciónResultado correcto
Docker al arrancarenabled y enabled
IT-ToolsUp, puerto 127.0.0.1:8081->80/tcp
Puertos80 y 443 en 0.0.0.0 y [::] (nginx); 8081 solo en 127.0.0.1 (docker-proxy)
Nginxsyntax is ok sin avisos
RenovaciónLos dos certificados en succeeded
Desde tu PChttps://herramientas.TU_DOMINIO funciona; http://TU_IP:8081 no

Reinicia el servidor y, al volver a entrar, comprueba que IT-Tools ha arrancado sola:

En el servidor
sudo reboot
# ...espera un minuto, ssh lab, y:
docker ps

Después, en IONOS, crea la instantánea 03-docker-proxy.

✓

Comprueba antes de seguir

Ya sabes publicar cualquier aplicación en contenedor con su subdominio y HTTPS. La siguiente guía (episodio 10) monta WordPress con Docker Compose detrás de este proxy, con base de datos, volúmenes y protección del formulario de acceso.

Anexos

Para cuando algo no sale

Con contenedores, el orden de búsqueda es: docker compose ps (¿está en marcha?), docker compose logs (¿qué dice?), curl a 127.0.0.1 (¿responde por dentro?) y, por último, el error.log de Nginx.

Anexo A · Problemas frecuentes

Docker

Lo que vesQué pasaQué hacer
permission denied while trying to connect to the Docker daemon socketTu usuario no está en el grupo docker, o no has vuelto a entrar desde que lo añadistegroups; si falta docker, paso 5 y cierra y abre la sesión
Cannot connect to the Docker daemon. Is the docker daemon running?El servicio de Docker está parado, a menudo por un error en daemon.jsonsudo journalctl -u docker -n 30 --no-pager; revisa el JSON del paso 6
docker compose: no configuration file provided: not foundNo estás en la carpeta de la aplicacióncd /opt/apps/NOMBRE, o usa docker compose -f /ruta/compose.yaml ...
yaml: line N: ...Sangría mal hecha o tabuladores en el YAMLRevisa la línea N y la anterior; solo espacios
Bind for 127.0.0.1:8081 failed: port is already allocatedOtro contenedor ya usa ese puerto del servidordocker ps para ver quién; elige otro puerto (8082...) y lleva la lista
STATUS: Restarting (1) ...El contenedor arranca y se cae una y otra vezdocker compose logs --tail 50: el motivo está en las últimas líneas
manifest unknown / not found al descargarLa etiqueta de la imagen está mal escrita o no existeCompruébala en la pestaña Tags de Docker Hub (paso 11)
El disco se llenaImágenes viejas, contenedores parados o registrosdocker system df; docker image prune -a; revisa el paso 6

Nginx como proxy

Lo que vesQué pasaQué hacer
502 Bad GatewayNginx no consigue hablar con la aplicación: el contenedor está parado o el puerto de proxy_pass no coincidedocker compose ps y curl -I http://127.0.0.1:8081 en el servidor. El puerto de proxy_pass debe ser la primera cifra de ports (8081), no la del contenedor (80)
504 Gateway TimeoutLa aplicación tarda más que proxy_read_timeoutMira los registros de la aplicación; si es normal que tarde, sube el valor en ese sitio
ERR_TOO_MANY_REDIRECTSLa aplicación cree que está en HTTP y redirige a HTTPS, y Nginx le vuelve a mandar HTTPComprueba que el sitio incluye snippets/proxy.conf (cabecera X-Forwarded-Proto)
La página carga pero no se actualiza en tiempo real, o dice «desconectado»Falla el WebSocketRevisa conf.d/websocket.conf y las líneas Upgrade/Connection del snippet
unknown "connection_upgrade" variable en nginx -tFalta el map del paso 13.1Crea /etc/nginx/conf.d/websocket.conf
Sale tu web principal en vez de la aplicaciónEl enlace del sitio no está en sites-enabled, o el server_name no coincidels -l /etc/nginx/sites-enabled/ y revisa el nombre

Anexo B · Puertos abiertos al terminar esta guía

PuertoServicioFirewall de IONOSUFWQué lo protege
22/TCPSSHAbiertoAbiertoSolo clave, sin root, fail2ban (Guía 01)
80/TCPHTTP (Nginx)AbiertoAbiertoSolo Let's Encrypt y redirección (Guía 02)
443/TCPHTTPS (Nginx)AbiertoAbiertoTLS, cabeceras, rechazo de nombres ajenos (Guía 02)
8081/TCPIT-Tools (contenedor)Cerrado—Escucha solo en 127.0.0.1: inaccesible desde fuera aunque se abriera en un cortafuegos

Lista de puertos internos

Lleva esta lista para no repetir puertos. La iremos ampliando en cada guía:

Puerto internoAplicaciónSubdominio
127.0.0.1:8081IT-Toolsherramientas.TU_DOMINIO
127.0.0.1:8082(WordPress, episodio 10)
127.0.0.1:8083

Anexo C · Chuleta de Docker y Compose

Para...Comando
Arrancar o aplicar cambios de una aplicacióncd /opt/apps/APP && docker compose up -d
Estado de la aplicacióndocker compose ps
Registros en directodocker compose logs -f (sal con Ctrl+C)
Últimas líneas del registrodocker compose logs --tail 50
Reiniciar la aplicacióndocker compose restart
Parar y quitar los contenedores (los datos en volúmenes se quedan)docker compose down
Actualizar a la versión del archivodocker compose pull && docker compose up -d
Ver la configuración final que usa Composedocker compose config
Todos los contenedores del servidordocker ps -a
Política de reinicio de todosdocker inspect -f '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -q)
Entrar en un contenedordocker exec -it NOMBRE sh
Uso de CPU y memoria en directodocker stats (sal con Ctrl+C)
Espacio que ocupa Dockerdocker system df
Limpiar imágenes sin usardocker image prune -a
★

Si te atascas

Pega: el compose.yaml, la salida de docker compose ps, las últimas líneas de docker compose logs y, si el problema es al entrar por el navegador, el sitio de Nginx y su error.log.

✓

¿Prefieres que lo haga yo?

Si no tienes tiempo o prefieres no tocar tu servidor, escríbeme y te lo dejo montado.