Entorn de pràctica DAM — Nginx (proxy invers) + Docker (web8080 & Portainer) + Certbot

Domini: dam0.dam.inspedralbes.cat · Arrel servida pel contenidor web8080 (port 8080) · Portainer sota /portainer

Arquitectura

Client ──HTTPS(443)──► Nginx (host)
  ├─► /           ─► proxy_pass http://127.0.0.1:8080  → web8080 (nginx:alpine, volum: /data/www → /usr/share/nginx/html)
  └─► /portainer/ ─► proxy_pass http://127.0.0.1:9000/ → portainer (HTTP intern 9000, --base-url /portainer)
          
Docker Nginx Certbot / Let's Encrypt Reverse Proxy WebSockets
Objectiu didàctic: separar responsabilitats entre proxy (Nginx), serveis en contenidor (web estàtic i Portainer) i gestió de certificats (Certbot).

Prèvies

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 80,443/tcp
      
Ports oberts: 80/tcp 443/tcp

Contenidors Docker

1) Web estàtic (port 8080) amb volum

sudo mkdir -p /data/www
echo '<h1>Hola dam0!</h1>' | sudo tee /data/www/index.html

sudo docker run -d \
  --name web8080 \
  --restart=always \
  -p 8080:80 \
  -v /data/www:/usr/share/nginx/html:ro \
  nginx:alpine
      

2) Portainer sota subruta /portainer (HTTP intern)

sudo mkdir -p /data/portainer

# (recrear si cal)
sudo docker stop portainer 2>/dev/null || true
sudo docker rm portainer 2>/dev/null || true

sudo docker run -d \
  --name portainer \
  --restart=always \
  -p 8000:8000 \
  -p 9000:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /data/portainer:/data \
  portainer/portainer-ce:latest \
  --http-enabled \
  --base-url /portainer
      
A Portainer → Settings → Public URL: https://dam0.dam.inspedralbes.cat/portainer

Estat esperat (exemple)

docker ps --format "table {{.Names}}\t{{.Ports}}"

NAMES       PORTS
portainer   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 9443/tcp
web8080     0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
      

Configuració Nginx (proxy invers)

Fitxer: /etc/nginx/sites-available/dam0.dam.inspedralbes.cat (enllaç a sites-enabled)

server {
    listen 443;
    server_name dam0.dam.inspedralbes.cat;

    # Arrel → contenidor web (8080)
    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }

    # Redirecció /portainer → /portainer/
    location = /portainer {
        return 301 /portainer/;
    }

    # Portainer sota subruta (HTTP backend 9000)
    location ^~ /portainer/ {
        proxy_pass http://127.0.0.1:9000/;  # barra final per fer strip del prefix
        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 Connection         "";
    }

    # WebSocket Portainer (terminal, logs live)
    location ^~ /portainer/api/websocket/ {
        proxy_pass http://127.0.0.1:9000/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade            $http_upgrade;
        proxy_set_header Connection         "upgrade";
        proxy_set_header Host               $host;
        proxy_set_header X-Forwarded-Proto  $scheme;
    }
    ssl_certificate /etc/letsencrypt/live/dam0.dam.inspedralbes.cat/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/dam0.dam.inspedralbes.cat/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = dam0.dam.inspedralbes.cat) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name dam0.dam.inspedralbes.cat;
    return 404; # managed by Certbot


}


      
# activar site i desactivar el per defecte
sudo ln -sf /etc/nginx/sites-available/dam0.dam.inspedralbes.cat /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
      

Certificat digital (Let's Encrypt amb Certbot)

sudo certbot --nginx -d dam0.dam.inspedralbes.cat --redirect -m pprats@inspedralbes.cat --agree-tos
      
Certbot crea i renova automàticament el certificat (timers del sistema). Prova de renovació:
sudo certbot renew --dry-run
      

Verificacions

# local (HTTP)
curl -I http://127.0.0.1/
curl -I http://127.0.0.1/portainer/

# públic (HTTPS)
curl -I https://dam0.dam.inspedralbes.cat/
curl -I https://dam0.dam.inspedralbes.cat/portainer/
      

Exemple de respostes correctes

HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
...

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
# (Portainer servit via subruta)
...
      

Checklist de ports i firewall

# qui escolta a 80/443?
sudo ss -lntp | grep -E ':(80|443)\b' || echo "Res escoltant a 80/443"

# UFW
sudo ufw status verbose
sudo ufw allow 80,443/tcp
sudo ufw reload
      

Troubleshooting

1) 404 a /portainer/

2) Terminal/logs no funcionen

3) HTTPS no connecta des de fora

4) Comentaris en comandes multi-línia

Scripts “tot en un” (opcional per aula)

Deploy bàsic

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

# Paquets i firewall
apt update
apt install -y nginx certbot python3-certbot-nginx
ufw allow 80,443/tcp || true

# Web 8080
mkdir -p /data/www
echo '<h1>Hola dam0!</h1>' > /data/www/index.html
docker run -d --name web8080 --restart=always -p 8080:80 -v /data/www:/usr/share/nginx/html:ro nginx:alpine

# Portainer HTTP a /portainer
mkdir -p /data/portainer
docker rm -f portainer 2>/dev/null || true
docker run -d --name portainer --restart=always \
  -p 8000:8000 -p 9000:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /data/portainer:/data \
  portainer/portainer-ce:latest \
  --http-enabled --base-url /portainer

# Nginx (site)
cat >/etc/nginx/sites-available/dam0.dam.inspedralbes.cat <<'CONF'
server {
    listen 80;
    server_name dam0.dam.inspedralbes.cat;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }

    location = /portainer { return 301 /portainer/; }

    location ^~ /portainer/ {
        proxy_pass http://127.0.0.1:9000/;
        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 Connection         "";
    }

    location ^~ /portainer/api/websocket/ {
        proxy_pass http://127.0.0.1:9000/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade            $http_upgrade;
        proxy_set_header Connection         "upgrade";
        proxy_set_header Host               $host;
        proxy_set_header X-Forwarded-Proto  $scheme;
    }
}
CONF

ln -sf /etc/nginx/sites-available/dam0.dam.inspedralbes.cat /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx

# Certbot
certbot --nginx -d dam0.dam.inspedralbes.cat --redirect -m pprats@inspedralbes.cat --agree-tos
      
Ajusta domini/email si cal.

Apèndix: snippets útils

# comprovar serveis al 80/443
sudo ss -lntp | grep -E ':(80|443)\b' || echo "Res escoltant"

# logs Nginx
sudo journalctl -u nginx -n 200 --no-pager

# recrear Portainer si canvies la base-url
docker rm -f portainer
docker run -d --name portainer --restart=always \
  -p 8000:8000 -p 9000:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /data/portainer:/data \
  portainer/portainer-ce:latest \
  --http-enabled --base-url /portainer
      

Crèdits i versions

  • Nginx (host) — 1.24.x (Ubuntu/Debian repo)
  • Certbot — python3-certbot-nginx
  • Docker images — nginx:alpine, portainer/portainer-ce:latest