El VPS expone públicamente sólo HTTP/HTTPS por Caddy. Servicios internos quedan detrás de Caddy o en red Docker.
Reverse proxy (Caddy)
manual.lt360.pro→ static files en/srv/manual(host:/home/gonzalo/docs/manual-interno). Raíz: redirección a/latest.html(symlink).studio.lt360.pro→reverse_proxy pgadmin:80(PgAdmin en Docker).postgrest.lt360.pro→reverse_proxy lt360-postgrest:3000con CORS abierto y cabeceras de seguridad.monitoreo.lt360.pro→ Netdata detrás de basic_auth + TLS.- Otros presentes en
/etc/caddy/sites/:aiteam.caddy,n8n.caddy,portainer.caddy(no modificados en esta sesión).
TLS: Let’s Encrypt automático desde Caddy; certificados emitidos para studio, postgrest, monitoreo.
Puertos expuestos (host)
- Abiertos:
80/tcp,443/tcp(Caddy). También9000/tcp(Portainer) y9100–9101/tcp(MinIO) (pendiente de securizar tras Caddy, ver mejoras). - Cerrados tras cambios:
2019/tcp(admin Caddy),3000/tcp(PostgREST),6333/tcp(Qdrant),19999/tcp(Netdata). - Regla extra en
iptables(cadenaDOCKER-USER) bloquea19999/tcpcomo defensa en profundidad.
Carpetas clave
- Caddyfile (host):
/home/gonzalo/caddy/Caddyfile - Sites Caddy (host):
/home/gonzalo/caddy/sites/→ contenedor:/etc/caddy/sites - Manual (host):
/home/gonzalo/docs/manual-interno→ contenedor:/srv/manual - Stacks Docker:
/home/gonzalo/lt360/stacks/(caddy-proxy,api-postgrest,corp-swarm,monitoring)
Red Docker
Red principal: lt360-global. Caddy y servicios (PgAdmin, PostgREST, Netdata, Qdrant, Nextcloud*, MinIO, etc.) comparten esta red para service discovery por nombre.
(*) Nextcloud tiene su stack y servicios auxiliares propios.
Cabeceras de seguridad
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: no-referrerPermissions-Policy: geolocation=(), camera=(), microphone=()
Inventario de contenedores (vista rápida)
| Nombre | Imagen | Notas |
|---|---|---|
| caddy | caddy:latest | Escucha 80/443, admin 2019 deshabilitado |
| pgadmin | dpage/pgadmin4:8.7 | Tras studio.lt360.pro |
| lt360-postgrest | postgrest/postgrest:latest | Exposición directa 3000 cerrada; detrás de Caddy |
| lt360-qdrant | qdrant/qdrant:latest | Exposición directa 6333 cerrada; usar Caddy si se publica |
| netdata | netdata/netdata | 19999 cerrado; detrás de monitoreo.lt360.pro + basic_auth |
| lt360-minio | minio/minio:latest | Host: 9100→9000, 9101→9001 (pendiente de mover detrás de Caddy) |
| portainer | portainer/portainer-ce:latest | Host: 9000 (pendiente de restringir/tras Caddy) |
| Nextcloud (+onlyoffice, db, redis, web, cron) | nextcloud:28-fpm, nginx:alpine, … | Stack propio |
| corp-swarm-postgres-1 | postgres:15-alpine | DB corporativa |
Notas basadas en la sesión del 25–26 Ago 2025.
25–26 Ago 2025
- Caddy: se deshabilita
admin→ puerto2019cerrado. Se elimina mapeo 2019 endocker-composey se recrea contenedor. - studio.lt360.pro: nuevo vhost (proxy a
pgadmin:80). Certificado LE emitido correctamente. - postgrest.lt360.pro: nuevo vhost (proxy a
lt360-postgrest:3000) con CORS y cabeceras de seguridad. Se elimina mapeo de puerto3000en el host. - monitoreo.lt360.pro: Netdata tras Caddy con basic auth + TLS. Se corrige uso de
basicauth→basic_auth. Se elimina exposición directa19999y se añade reglaiptablesde bloqueo. - Qdrant: se quita mapeo de
6333en el host; uso por red interna. - manual.lt360.pro: se prepara esta página como manual unificado (snapshot + historial) y se propone apuntar
latest.htmlavps-manual.html.
- Editar esta misma página en
/home/gonzalo/docs/manual-interno/vps-manual.html. - Actualizar la sección Estado actual cuando cambie la configuración (puertos, vhosts, cabeceras, etc.).
- Añadir una entrada en Historial de cambios con fecha, detalle y motivo.
- La portada del manual (
/latest.html) apunta a este archivo; no requiere recargar Caddy.
Sugerencias: añadir HSTS en sitios públicos; limitar CORS de PostgREST a orígenes reales; mover MinIO y Portainer detrás de Caddy o restringir acceso por firewall/VPN.
Última actualización: 2025-08-26
1. Configuración del Servidor Web (Caddy)
- Configuración principal (
/home/gonzalo/caddy/Caddyfile):- admin:
off - email:
gonzalo@lt360.pro - sitios:
import /etc/caddy/sites/*.caddy
- admin:
- Manual (
/home/gonzalo/caddy/sites/manual.caddy):- dominio:
manual.lt360.pro - raíz:
/srv/manual(volumen desde/home/gonzalo/docs/manual-interno) - archivo por defecto: redirección a
/latest.htmlytry_files {path} /latest.html - file_server habilitado
- dominio:
2. Directorio y Archivos del Manual
- Host:
/home/gonzalo/docs/manual-interno/ - Contenedor (Caddy):
/srv/manual - latest.html: enlace simbólico a
vps-manual.html(página actualmente servida) - vps-manual.html: documento principal (estilo y formato integrados); contiene snapshot y secciones históricas
3. Automatización
- Existe un script de generación (
/home/gonzalo/docs/manual-interno/generar_manual.sh) que:- Recopila datos del sistema (CPU, RAM, OS, contenedores/puertos, etc.).
- Rinde un HTML con la plantilla y la información actualizada.
- Actualiza el enlace
latest.htmlpara apuntar al HTML más reciente.
Última actualización: 2025-08-26
1. Configuración del Servidor Web (Caddy)
- /home/gonzalo/caddy/Caddyfile:
admin off, emailgonzalo@lt360.pro,import /etc/caddy/sites/*.caddy - /home/gonzalo/caddy/sites/manual.caddy: dominio
manual.lt360.pro, raíz/srv/manual,try_files {path} /latest.html,file_server
2. Directorio y Archivos del Manual
- Host:
/home/gonzalo/docs/manual-interno/; Contenedor:/srv/manual latest.htmlenlaza avps-manual.html(enlace relativo recomendado)vps-manual.htmles la página principal con snapshot e historial
3. Automatización
- Script sugerido:
/home/gonzalo/docs/manual-interno/generar_manual.sh(recoge datos, rinde HTML, actualizalatest.html)
Instantánea de la configuración actual (contenido sensible redactado). Para credenciales reales, almacenar/consultar en /home/gonzalo/vault-lt360/credenciales.md.gpg.
/home/gonzalo/caddy/Caddyfile— 5 líneas — sha256 9e25bf21ab161be7f577d789bca2b4762702b593b414dbfc7e00e43fbd54755b{ admin off email gonzalo@lt360.pro } import /etc/caddy/sites/*.caddy/home/gonzalo/caddy/sites/aiteam.caddy— 20 líneas — sha256 0164607b18946cb8c8a85a382ba0e9c7bff5d66e385525cc824e61dfc0a79049aiteam.lt360.pro { encode gzip zstd # /hooks/* → strip del prefijo y reenvío a /webhook/{resto} handle_path /hooks/* { rewrite * /webhook{path} reverse_proxy http://corp-swarm-n8n-1:5678 } # Respuesta simple para GET / handle { respond "AITeam endpoint activo" 200 } header { X-Content-Type-Options nosniff Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/manual.caddy— 12 líneas — sha256 ced2b6341200680308ac33fc41cefa9b31bc83cc2efaa60ff6e83770e2ad8285manual.lt360.pro { root * /srv/manual file_server try_files {path} /latest.html encode gzip zstd header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/minio.caddy— 15 líneas — sha256 4c5c1751bbb687313aa8cf53cde0ce19c6848ef30557281d30169fdbd0a2a867minio.lt360.pro { # Protección basic_auth { gonzalo@lt360.pro REDACTED_BCRYPT } reverse_proxy lt360-minio:9001 header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/monitoreo.caddy— 6 líneas — sha256 753dbbb17eef1e2c182130edbbb5f05fd2cf09f11a0be9bfe2792149c65bd733monitoreo.lt360.pro { basic_auth { gonzalo@lt360.pro REDACTED_BCRYPT } reverse_proxy netdata:19999 }/home/gonzalo/caddy/sites/n8n.caddy— 11 líneas — sha256 ab52c7f630c753ef23ba244f9f63747d900d4f73539ad0f51a1a8b9f3e150f13n8n.lt360.pro { encode gzip zstd reverse_proxy http://corp-swarm-n8n-1:5678 header { X-Content-Type-Options nosniff Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/portainer.caddy— 14 líneas — sha256 2f5d7ef911b03f446647da5e5e66e309ebedfd1bb16a20ad5ad13ff4f0a59c29portainer.lt360.pro { basic_auth { gonzalo@lt360.pro REDACTED_BCRYPT } reverse_proxy portainer:9000 header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/postgrest.caddy— 22 líneas — sha256 8edda4a604fc9deab37ef9dbbad8a580b2cd0e33b9ff5bc659b51314c7f373c1postgrest.lt360.pro { @preflight method OPTIONS handle @preflight { header Access-Control-Allow-Origin "*" header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS" header Access-Control-Allow-Headers "*" header Access-Control-Max-Age "86400" respond "" 204 } reverse_proxy lt360-postgrest:3000 header { Access-Control-Allow-Origin "*" Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS" Access-Control-Allow-Headers "*" X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy no-referrer Permissions-Policy "geolocation=(), camera=(), microphone=()" } }/home/gonzalo/caddy/sites/studio.caddy— 3 líneas — sha256 3e39aa3367196310461ed4a26ff506130ee83c754760b5a1db38e32f26206625studio.lt360.pro { reverse_proxy pgadmin:80 }/home/gonzalo/lt360/stacks/api-postgrest/docker-compose.yml— 22 líneas — sha256 d4699852255763b14bef2e9686bfe57bb0cfbae48a4cd2c5fcba59e0ca303813services: lt360-postgrest: image: postgrest/postgrest:latest container_name: lt360-postgrest restart: unless-stopped env_file: .env environment: PGRST_DB_URI: "postgres://lt360:k3X2Y9z8aB7mQ6wP1nR5vC4xS0tL8qM2@lt360-db:5432/lt360_main" PGRST_DB_SCHEMAS: "public" PGRST_DB_ANON_ROLE: "anon" PGRST_DB_POOL: "10" PGRST_DB_PREPARED_STATEMENTS: "false" PGRST_DB_CHANNEL_ENABLED: "true" PGRST_DB_CHANNEL: "pgrst" PGRST_SERVER_PROXY_URI: "https://postgrest.lt360.pro" PGRST_OPENAPI_SERVER_PROXY_URI: "https://postgrest.lt360.pro" PGRST_CORS_ALLOWED_ORIGINS: "*" PGRST_CORS_MAX_AGE: "86400" networks: default: external: true name: lt360-global/home/gonzalo/lt360/stacks/caddy-proxy/docker-compose.override.yml— 6 líneas — sha256 bee74ef25945ddd108d862e72772996e8aa63ce680f805671af1a1fb011b17baservices: caddy: volumes: - /home/gonzalo/caddy/Caddyfile:/etc/caddy/Caddyfile - /home/gonzalo/caddy/sites:/etc/caddy/sites - /home/gonzalo/docs/manual-interno:/srv/manual:ro/home/gonzalo/lt360/stacks/caddy-proxy/docker-compose.yml— 15 líneas — sha256 517c2ebe7a4b07ffa51e1f4fa8ecda2c284e8e4b29134c77b61cec2d21184e38services: caddy: image: caddy:latest container_name: caddy restart: unless-stopped # Exponemos los mismos puertos para HTTPS/HTTP y la API admin (2019) ports: - "80:80" - "443:443" # Reutilizamos la red actual para que siga viendo lt360-n8n, qdrant, minio, etc. networks: default: external: true name: lt360-global/home/gonzalo/lt360/stacks/corp-swarm/docker-compose.override.yml— 3 líneas — sha256 49575eae711a95d74dd7b626341823d85622db29febb9feb606111992e8d8beaservices: minio: ports: []/home/gonzalo/lt360/stacks/corp-swarm/docker-compose.yml— 13 líneas — sha256 5b7b008f9bf3fcc21a262af9256ba986f23674d173a15d7b295fb2a31729bbf2services: qdrant: image: qdrant/qdrant:latest container_name: lt360-qdrant restart: unless-stopped environment: QDRANT__SERVICE__API_KEY: REDACTED volumes: - ./data/qdrant:/qdrant/storage networks: default: external: true name: lt360-global/home/gonzalo/lt360/stacks/monitoring/docker-compose.yml— 5 líneas — sha256 13e6d740c2e5c934306f13eb1c177a53869761e8e2c6cae4a1d66f0bf77de7a2services: netdata: image: netdata/netdata container_name: netdata hostname: netdata.lt360.pro/home/gonzalo/lt360/stacks/nextcloud/docker-compose.yml— 100 líneas — sha256 2a292b7e61a750ffb468da723446b56819551849d080f782dc1bebe8b9a94902services: db: image: mariadb:10.11 container_name: nextcloud-db restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=REDACTED - MYSQL_DATABASE=nextcloud - MYSQL_USER=gonzalo - MYSQL_PASSWORD=REDACTED volumes: - ./volumes/db:/var/lib/mysql redis: image: redis:7 container_name: nextcloud-redis restart: unless-stopped command: ["redis-server","--appendonly","yes"] volumes: - ./volumes/redis:/data app: image: nextcloud:28-fpm container_name: nextcloud-app restart: unless-stopped depends_on: - db - redis environment: - REDIS_HOST=nextcloud-redis - PHP_MEMORY_LIMIT=512M - PHP_UPLOAD_LIMIT=512M volumes: - ./volumes/nextcloud:/var/www/html cron: image: nextcloud:28-fpm container_name: nextcloud-cron restart: unless-stopped depends_on: - db - redis - app entrypoint: /cron.sh volumes: - ./volumes/nextcloud:/var/www/html web: image: nginx:alpine container_name: nextcloud-web restart: unless-stopped depends_on: - app expose: - "80" volumes: - ./volumes/nextcloud:/var/www/html - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro onlyoffice: image: onlyoffice/documentserver:8.1 container_name: nextcloud-onlyoffice restart: unless-stopped depends_on: - web environment: - JWT_ENABLED=false volumes: - onlyoffice_lib:/var/lib/onlyoffice - onlyoffice_pg:/var/lib/postgresql - onlyoffice_rmq:/var/lib/rabbitmq - onlyoffice_redis:/var/lib/redis - onlyoffice_logs:/var/log/onlyoffice - onlyoffice_fonts:/usr/share/fonts/truetype/custom - ./volumes/onlyoffice:/var/www/onlyoffice/Data networks: default: external: true name: lt360-global volumes: onlyoffice_lib: external: true name: d373721ffe80839f23c8846c50ab802ea19265c0fe540e7096566549f2a52585 onlyoffice_pg: external: true name: 07d880372d8483987de566a733d827e11b5b4c44b7c97cf83cf2023deb9fad13 onlyoffice_rmq: external: true name: cfb2be709e33ad4392f562b3bb73a50fd52923438f41adb1b543efcea3c1ca52 onlyoffice_redis: external: true name: 11b03e28353a7ca1e30521af7294358aadcd30bca60b55486cea51a5e1e1e2a3 onlyoffice_logs: external: true name: efdb74d0af5331fc096cc7720de2188665f221a738d9c716742c2136a79ed8c2 onlyoffice_fonts: external: true name: 40be04d24e7847f58d148d3813193c7ce3ccfc84f3fc1271f7a45b75a939c3dbiptables -S DOCKER-USER(sin reglas o iptables no disponible)