El día 1 convierte la base Debian en una plataforma completa: OpenCloud sobre Docker Compose con overlays oficiales, Nginx terminando TLS y encaminando sólo al loopback, políticas de firewall coherentes y la landing Astro del proyecto publicada como segundo backend detrás del mismo frontal.

También se introducen mejoras posteriores al primer corte — Fail2ban y el subdominio dedicado del cloud — porque forman parte del relato operativo real del despliegue.

OpenCloud “core” sin Collabora/WOPI

El modo elegido arranca OpenCloud como servicio único desde la composición oficial (opencloud-eu/opencloud-compose), usando una imagen rodante etiquetada (opencloud-rolling con tag fijado en despliegue) para poder actualizar de forma deliberada (docker compose pull + ventana de mantenimiento).

  • Overlay external-proxy/opencloud.yml: adapta variables como PROXY_HTTP_ADDR para escuchar dentro del contenedor y publicar el puerto HTTP del proxy sólo como 127.0.0.1:<puerto-app> en el host.
  • COMPOSE_PROJECT_NAME=opencloud: ancla los nombres de volumen Docker (opencloud_opencloud-data, opencloud_opencloud-config) sin depender del cwd.
  • Fichero .env: fuente única de variables de despliegue; permisos estrictos en disco y fuera del control de versiones.
  • COMPOSE_FILE: lista los overlays necesarios (docker-compose.yml base más el overlay de proxy externo).

Dentro del contenedor coexisten microservicios que conversan por gRPC/HTTP en localhost interno del propio contenedor; ese rango no se expone directamente al host salvo por los endpoints previstos por el chart upstream.

Diagrama detallado OpenCloud tras el proxy

Browser
   │  HTTPS :443
   ▼
Nginx (Debian, site dedicado OpenCloud)
   │  HTTP http://127.0.0.1:9200  (sólo loopback — no sale del host)
   ▼
Contenedor OpenCloud (usuario UID/GID fijo en despliegue)
   │  microservicios internos en 127.0.0.1 (rango ~9140–9300 dentro del contenedor)
   ▼
Volúmenes Docker:
   • opencloud-data   → ficheros de usuarios, índices, NATS, IDM...
   • opencloud-config → opencloud.yaml autogenerado, CSP, listas de política...

PROXY_TLS=false indica que la terminación TLS ocurre fuera del contenedor (en Nginx). OpenCloud genera URLs coherentes cuando recibe cabeceras X-Forwarded-* correctas.

Mapa de puertos y superficie expuesta

PuertoListenerAlcanceRol
22sshdInternet (según política)Administración SSH.
80NginxInternetHTTP para redirección a HTTPS y retos ACME.
443NginxInternetTLS + virtual hosts (KM0 + OpenCloud).
9200Docker → OpenCloud proxySólo 127.0.0.1Backend HTTP que ve Nginx.
9140–9300Microservicios OpenCloudInterior del contenedorgRPC/HTTP internos — no publicados en el host.

UFW refuerza la política permitiendo desde Internet sólo lo necesario (SSH + HTTP/S típicamente). La regla mental es: si no debe conocerlo el navegador externo, no escucha en todas las interfaces.

Nginx ↔ OpenCloud: por qué importan ciertas directivas

DirectivaValor típicoMotivo
proxy_bufferingoffSSE (Server-Sent Events) para actualizaciones en tiempo real del cliente web.
proxy_request_bufferingoffSubidas resumibles TUS — hay que streamear sin bufferizar todo el cuerpo.
proxy_passhttp://127.0.0.1:9200HTTP local; TLS ya resuelto en el borde.
X-Forwarded-Proto$schemeOpenCloud genera redirects/cookies coherentes para HTTPS.
Upgrade / ConnectionpassthroughWebSockets para componentes interactivos del UI.
proxy_read_timeout / proxy_send_timeout3600sSesiones largas de sincronización y subidas grandes.
client_max_body_size10GTope útil para ficheros grandes vía cliente.
http2onMultiplexación HTTP/2 en Nginx moderno.

Árbol de despliegue en /opt/opencloud

/opt/opencloud/
├── opencloud-compose/          # clon upstream opencloud-eu/opencloud-compose (git pull para overlays base)
│   ├── docker-compose.yml
│   ├── external-proxy/opencloud.yml   # overlay loopback + PROXY_HTTP_ADDR
│   ├── .env                           # activo — fuera de git, chmod 600
│   └── .env.debian-core-external-proxy.example
├── nginx/
│   ├── sites-available/opencloud      # plantilla TLS + proxy (Let's Encrypt en prod)
│   └── certbot-challenge.site-example # HTTP temporal para primera emisión
├── scripts/
│   └── backup-volumes.sh              # backup .tar.gz de volúmenes (sin pg_dump en modo core)
└── docs/
    └── runbook.md                     # operación diaria (compose, upgrades, backups)

Los snippets en repo sirven como referencia; los ficheros activos bajo /etc/nginx/sites-available/ en el host deben revisarse siempre con nginx -t antes de systemctl reload nginx.

Variables de entorno relevantes (concepto, sin valores secretos)

  • COMPOSE_FILE / COMPOSE_PROJECT_NAME: definen overlays activivos y nombre estable del proyecto Docker.
  • OC_DOMAIN: debe coincidir con el fqdn público; durante una fase inicial puede configurarse como dirección IP, pero algunos enlaces del UI se comportan mejor con nombre DNS definitivo.
  • INSECURE: en laboratorio puede relajar validación TLS entre microservicios cuando los certificados aún no son de una AC reconocida por todos los runtime; en producción debe alinearse con una cadena TLS limpia.
  • OC_DOCKER_IMAGE / OC_DOCKER_TAG: pinchan versión reproducible antes de upgrades deliberados.
  • LOG_DRIVER / LOG_LEVEL: compatibilidad con política de logs del daemon Docker.
  • DEMO_USERS: control explícito sobre usuarios de demostración.

La primera configuración administrativa debe completarse desde la interfaz y cualquier detalle sensible queda fuera de canales públicos como este blog.

Dónde viven los datos (volúmenes Docker)

OpenCloud centraliza persistencia en dos volúmenes nombrados. El contenido relevante incluye:

  • idm/ — directorio LDAP interno embebido y material criptográfico asociado.
  • idp/ — estado del proveedor OIDC.
  • nats/ — bus de eventos JetStream entre microservicios.
  • search/ — índice full-text (Bleve).
  • storage/metadata — metadatos CS3 (espacios, bloqueos, comparticiones).
  • storage/users/.../.oc-nodes/ — nodos/blobs del driver “decomposed”: los ficheros lógicos no son un árbol clásico en disco sino nodos referenciados por metadatos.
  • web/ — activos/cache estáticos que usa el frontal integrado.
  • Volumen de config runtimeopencloud.yaml generado automáticamente, CSP auxiliar y políticas auxiliares.

Cifrado en reposo: por defecto los blobs son ficheros ordinarios dentro del volumen. Opciones típicas de endurecimiento: cifrado de disco del host (LUKS), backend objeto con SSE en proveedor compatible (overlay S3), o cifrado extremo-a-extremo en clientes de escritorio/móvil donde las llaves no salen del dispositivo.

Cifrado en tránsito: TLS cliente↔Nginx; LDAP interno puede usar TLS con certificados generados para servicios IDM según la configuración automática.

Flujo HTTPS del sitio KM0 (km0-web)

Internet :443 ─► Nginx host (TLS, km0.amvara.de)
                     │
                     └──► http://127.0.0.1:9180  (contenedor km0-web — sólo loopback)
                            Astro estático servido por nginx Alpine dentro del contenedor
  • Stack: Astro 5 + Tailwind 3, salida estática (build.format orientado a directorios).
  • i18n: JSON en src/i18n/ + rutas dedicadas (/ca/, /en/), español por defecto en raíz.
  • Build: Node 22 Alpine multi-stage (npm ci, astro build).
  • Repo servidor: típicamente /opt/km0-web con docker compose build && docker compose up -d tras cambios.
  • SEO: @astrojs/sitemap, alternativas hreflang.

Comando rápido de verificación tras despliegue: cabeceras HTTP a las tres rutas principales desde localhost para aislar fallos del proxy TLS (curl -sI http://127.0.0.1:9180/ etc.).

Fail2ban y subdominio del cloud

Tras el primer corte estable se añadió Fail2ban como red complementaria al firewall: observa journals/patrón de logs de servicios expuestos y aplica bans temporales ante comportamiento agresivo repetido.

El cloud quedó publicado en cloud.km0.amvara.de, separado explícitamente de la marca de marketing en km0.amvara.de. Ventajas:

  • certificados y políticas CSP pueden divergir;
  • los usuarios entienden qué URL usar para trabajo vs comunicación;
  • los equipos pueden delegar DNS/TLS sin mezclar configuraciones del Astro estático.

Operación rutinaria (extracto)

cd /opt/opencloud/opencloud-compose
docker compose ps
docker compose logs -f opencloud
docker compose pull && docker compose up -d      # upgrade imagen pinchaada
git -C /opt/opencloud/opencloud-compose pull    # overlays upstream

ss -tulpn | grep -E ':22|:80|:443|:9200'       # sanity listeners
ufw status verbose

bash /opt/opencloud/scripts/backup-volumes.sh   # hasta automatizar cron

Los backups en modo core pueden centrarse en archivar los volúmenes Docker comprimidos; no depende de dumps SQL PostgreSQL porque la persistencia principal está integrada en los volúmenes del stack elegido.

Modo laboratorio vs producción

  • TLS provisional: certificado autofirmado útil para validar proxy y overlays — los navegadores mostrarán alertas hasta instalarse Let's Encrypt cuando exista DNS estable.
  • Dominio: pasar de IP cruda a FQDN mejora enlaces internos y cookies.
  • INSECURE relajado: sólo coherente mientras los certificados internos no forman parte de una PKI confiable para todos los procesos.
  • Backups automáticos: script manual hasta cron supervisado.
  • Renovación Certbot: vigilar certbot.timer en hosts productivos.

Nada de lo anterior sustituye revisiones periódicas de configuración fuera del control de versiones ni los procesos internos de rotación acordados por quien administra la plataforma.

Riesgos y siguientes pasos típicos

  • Mantener sano el ciclo de renovación TLS para cada hostname público.
  • Verificar tras cada cambio de Astro que el español por defecto sigue sirviendo correctamente según configuración i18n.
  • Toda edición del Nginx del host debe pasar por test + reload controlado.
  • Automatizar backups de volúmenes y ensayar restauraciones parciales.
  • Extender políticas Fail2ban con jails específicos cuando el patrón de ataques sea conocido.

Este texto amplía la narrativa técnica sin incluir secretos: cualquier valor sensible debe permanecer en el vault/runbook privado del equipo.