<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <title>El blog de Adrián JG</title>
    <link href="https://blog.adrianjg.es/feed.xml" rel="self" />
    <link href="https://blog.adrianjg.es" />
    <updated>2026-05-04T18:28:33+02:00</updated>
    <author>
        <name>Adrián JG</name>
    </author>
    <id>https://blog.adrianjg.es</id>

    <entry>
        <title>Configurando Diun para saber cuándo actualizar tus imágenes de Docker</title>
        <author>
            <name>Adrián JG</name>
        </author>
        <link href="https://blog.adrianjg.es/configurando-diun-para-saber-cuando-actualizar-tus-imagenes-de-docker/"/>
        <id>https://blog.adrianjg.es/configurando-diun-para-saber-cuando-actualizar-tus-imagenes-de-docker/</id>
        <media:content url="https://blog.adrianjg.es/media/posts/11/ian-taylor-HjBOmBPbi9k-unsplash-2.jpg" medium="image" />
            <category term="Self-hosting"/>

        <updated>2026-03-24T22:26:25+01:00</updated>
            <summary type="html">
                <![CDATA[
                        <img src="https://blog.adrianjg.es/media/posts/11/ian-taylor-HjBOmBPbi9k-unsplash-2.jpg" alt="" />
                    <p><strong>Diun</strong> se trata de una herramienta clave si utilizas <strong>Docker</strong>. Con ella, podrás recibir una notificación cuando exista una actualización de las imágenes de tus contenedores. En este artículo explicaré como ponerlo en marcha.</p>
<p> </p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <p><img src="https://blog.adrianjg.es/media/posts/11/ian-taylor-HjBOmBPbi9k-unsplash-2.jpg" class="type:primaryImage" alt="" /></p>
                <p><strong>Diun</strong> se trata de una herramienta clave si utilizas <strong>Docker</strong>. Con ella, podrás recibir una notificación cuando exista una actualización de las imágenes de tus contenedores. En este artículo explicaré como ponerlo en marcha.</p>
<p> </p>

<div class="post__toc">
<h3>Tabla de contenidos</h3>
<ul>
<li><a href="#mcetoc_1jmbrrq3e6">Introducción</a></li>
<li><a href="#mcetoc_1jmbvu8oi1a">Configurando Diun</a>
<ul>
<li><a href="#mcetoc_1jmid34qh3f">Zona horaria</a></li>
<li><a href="#mcetoc_1jmid34qh3g">Seguimiento de Docker</a></li>
<li><a href="#mcetoc_1jmid34qh3h">Integración con ntfy</a></li>
</ul>
</li>
<li><a href="#mcetoc_1jmbvu8oi1b">Habilitando el seguimiento de los contenedores</a></li>
</ul>
</div>
<p> </p>
<h2 id="mcetoc_1jmbrrq3e6">Introducción</h2>
<p>Hoy en día es esencial saber cuándo existe una actualización de software disponible, en especial en nuestros servidores expuestos a Internet. Las vulnerabilidades pueden aparecer cuando menos lo esperamos y nos pueden meter en un gran aprieto si no se actúa rápidamente actualizando a la última version.</p>
<p>Comentando un caso reciente, el framework <strong>Next.js</strong> sufrió de una <a href="https://nextjs.org/blog/CVE-2025-66478" target="_blank" rel="noopener noreferrer">vulnerabilidad</a> tan fácil de explotar como grave que causó estragos a numerosos servidores, los cuales terminaron afectados por malware por no actualizar a la versión que corregía los agujeros de seguridad.</p>
<p>Investigando qué opciones tenía para ayudarme con el mantenimiento de los contenedores de Docker, descubrí una herramienta llamada <a href="https://crazymax.dev/diun" target="_blank" rel="noopener noreferrer"><strong>Diun</strong></a>, la cual nos avisa con una notificación cuando se encuentra disponible una actualización.</p>
<p> </p>
<h2 id="mcetoc_1jmbvu8oi1a">Configurando Diun</h2>
<p>Oportunamente, Diun puede funcionar también como contenedor de Docker. Utilizaremos <strong>ntfy</strong> como servicio para recibir las notificaciones, el cual se explica cómo configurarlo en <a href="https://blog.adrianjg.es/notificaciones-push-privadas-para-tus-dispositivos-con-ntfy/">este artículo</a>.</p>
<p>Creamos una carpeta para Diun en nuestro servidor y en ella creamos un archivo compose.yaml con el siguiente contenido.</p>
<pre class="language-yaml line-numbers"><code>name: diun

services:
  diun:
    image: crazymax/diun:latest
    command: serve
    volumes:
      - "./data:/data"
      - "/var/run/docker.sock:/var/run/docker.sock"
    environment:
      - "TZ=Europe/Madrid"
      - "DIUN_WATCH_WORKERS=20"
      - "DIUN_WATCH_SCHEDULE=0 */6 * * *"
      - "DIUN_WATCH_JITTER=30s"
      - "DIUN_PROVIDERS_DOCKER=true"
      - "DIUN_NOTIF_NTFY_ENDPOINT=https://ntfy.ejemplo.com"
      - "DIUN_NOTIF_NTFY_TOKEN=token_ejemplo"
      - "DIUN_NOTIF_NTFY_TOPIC=tema_ejemplo"
    labels:
      - "diun.enable=true"
    restart: always</code></pre>
<p>En la sección de <code>volumes</code> mapearemos la carpeta de <code>data</code> en el mismo directorio para persistir los archivos de la aplicación. Es esencial también mapear el <code>/var/run/docker.sock</code> de nuestro host para que el contenedor pueda tener acceso a la información de los contenedores de Docker.</p>
<p>A continuación detallo los parámetros a configurar:</p>
<h3 id="mcetoc_1jmid34qh3f">Zona horaria</h3>
<p>Con la variable <code>TZ</code> indicamos la zona horaria de la aplicación, para que opere con consistencia a donde vivimos.</p>
<pre class="language-yaml line-numbers"><code>TZ=Europe/Madrid</code></pre>
<h3 id="mcetoc_1jmid34qh3g">Seguimiento de Docker</h3>
<p>Diun permite hacer un seguimiento desde distintos proveedores, incluyendo Kurbenetes, un archivo Dockerfile o un directorio que contenga archivos con extensión yaml.</p>
<p>Como queremos utilizarlo simplemente para las imágenes que están siendo utilizadas por nuestros contenedores de Docker, se incluye lo siguiente:</p>
<pre class="language-yaml line-numbers"><code>DIUN_PROVIDERS_DOCKER=true</code></pre>
<p>Podemos indicar un número máximo de agentes que realicen la comprobación con el siguiente parámetro. En mi caso, lo pongo a 20.</p>
<pre class="language-yaml line-numbers"><code>DIUN_WATCH_WORKERS=20</code></pre>
<p>La frecuencia de las comprobaciones se indica en sintaxis de cron. Para ejecutarlo cada 6 horas, lo indicaríamos así.</p>
<pre class="language-yaml line-numbers"><code>DIUN_WATCH_SCHEDULE=0 */6 * * *</code></pre>
<p>El siguiente valor es importante para "disimular" que estamos ejecutando un proceso automatizado a ojos del repositorio de Docker, para evitar posibles bloqueos por demasiados accesos concurrentes. Por cada ejecución de un agente, esperará un valor aleatorio de 0 a la duración indicada.</p>
<pre class="language-yaml line-numbers"><code>DIUN_WATCH_JITTER=30s</code></pre>
<h3 id="mcetoc_1jmid34qh3h">Integración con ntfy</h3>
<p>Indicamos el host donde se encuentra alojado nuestro servicio de ntfy.</p>
<pre class="language-yaml line-numbers"><code>DIUN_NOTIF_NTFY_ENDPOINT=https://ntfy.ejemplo.com</code></pre>
<p>A continuación, incluimos el token de aplicación de ntfy, el cual deberíamos haberlo incluido en el compose.yaml de dicho servicio.</p>
<pre class="language-yaml line-numbers"><code>DIUN_NOTIF_NTFY_TOKEN=token_ejemplo</code></pre>
<p>Por último, definimos el nombre del tema que usará Diun para enviar las notificaciones mediante ntfy.</p>
<pre class="language-yaml line-numbers"><code>DIUN_NOTIF_NTFY_TOPIC=tema_ejemplo</code></pre>
<p>Por último, levantamos el contenedor con docker compose.</p>
<pre class="language-bash line-numbers"><code>sudo docker compose up -d</code></pre>
<h2 id="mcetoc_1jmbvu8oi1b">Habilitando el seguimiento de los contenedores</h2>
<p>Como es posible que no nos interese hacer un seguimiento de todos los contenedores, debemos indicar en los compose.yaml de los que nos interese seguir la siguiente etiqueta para habilitarlo.</p>
<pre class="language-yaml line-numbers"><code>labels:
      - "diun.enable=true"</code></pre>
<p>Si por el contrario preferimos que automáticamente haga un seguimiento de todos los contenedores en ejecución, podemos indicar el siguiente parámetro al compose.yaml de Diun.</p>
<pre class="language-yaml line-numbers"><code>DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true</code></pre>
<p> </p>
<p>Aquí veríamos un ejemplo de notificación recibida.</p>
<figure class="post__image post__image--center"><img loading="lazy"  src="https://blog.adrianjg.es/media/posts/11/notificacion-diun.jpg" alt="" width="540" height="238" sizes="(max-width: 1920px) 100vw, 1920px" srcset="https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-xs.webp 640w ,https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-sm.webp 768w ,https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-md.webp 1024w ,https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-lg.webp 1366w ,https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-xl.webp 1600w ,https://blog.adrianjg.es/media/posts/11/responsive/notificacion-diun-2xl.webp 1920w"></figure>
<p>Como siempre, recomiendo echar un vistazo a la <a href="https://crazymax.dev/diun/config/" target="_blank" rel="noopener noreferrer">documentación oficial</a> para poder ajustar la configuración a las necesidades de cada uno.</p>
<p>Con estos pasos tendríamos lo necesario para recibir correctamente las notificaciones de las actualizaciones disponibles de nuestros contendedores. Personalmente, el tener esta información fácilmente accesible me da mucha tranquilidad a la hora de administrar mis servidores con Docker.</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Notificaciones push privadas para tus dispositivos con ntfy</title>
        <author>
            <name>Adrián JG</name>
        </author>
        <link href="https://blog.adrianjg.es/notificaciones-push-privadas-para-tus-dispositivos-con-ntfy/"/>
        <id>https://blog.adrianjg.es/notificaciones-push-privadas-para-tus-dispositivos-con-ntfy/</id>
        <media:content url="https://blog.adrianjg.es/media/posts/10/brett-jordan-jdm0UlDkOEI-unsplash.jpg" medium="image" />
            <category term="Self-hosting"/>

        <updated>2026-03-08T21:46:00+01:00</updated>
            <summary type="html">
                <![CDATA[
                        <img src="https://blog.adrianjg.es/media/posts/10/brett-jordan-jdm0UlDkOEI-unsplash.jpg" alt="" />
                    <p>Uno de mis servicios favoritos para alojar por la gran utilidad que aporta es <strong>ntfy</strong>, el cual nos permite disponer de un sistema de notificaciones personal. En este artículo explicaré cómo configurarlo con <strong>Docker</strong> y ejemplos para poderle sacar partido.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <p><img src="https://blog.adrianjg.es/media/posts/10/brett-jordan-jdm0UlDkOEI-unsplash.jpg" class="type:primaryImage" alt="" /></p>
                <p>Uno de mis servicios favoritos para alojar por la gran utilidad que aporta es <strong>ntfy</strong>, el cual nos permite disponer de un sistema de notificaciones personal. En este artículo explicaré cómo configurarlo con <strong>Docker</strong> y ejemplos para poderle sacar partido.</p>

<p> </p>
<div class="post__toc">
<h3>Tabla de contenidos</h3>
<ul>
<li><a href="#mcetoc_1jiljio8k3">Introducción</a></li>
<li><a href="#mcetoc_1jj7l4him2">Desplegando el servicio con Docker</a>
<ul>
<li><a href="#mcetoc_1jjot1nol13">Zona horaria</a></li>
<li><a href="#mcetoc_1jjoubtag3s">Configuración </a></li>
<li><a href="#mcetoc_1jjoubtag3t">Caché</a></li>
<li><a href="#mcetoc_1jjp1jhth178">Haciendo nuestro servicio de acceso privado</a></li>
<li><a href="#mcetoc_1jjp1jhth179">Configuración de usuarios</a></li>
<li><a href="#mcetoc_1jjp1jhth17a">Configuración de permisos</a></li>
<li><a href="#mcetoc_1jjp1jhth17b">Tokens de autenticación</a></li>
</ul>
</li>
<li><a href="#mcetoc_1jjot1nol14">Configurando Caddy como reverse proxy</a></li>
<li><a href="#mcetoc_1jjp1jhth17c">Cliente Android</a></li>
<li><a href="#mcetoc_1jjot1nol15">Ejemplos de uso</a></li>
</ul>
</div>
<p> </p>
<h2 id="mcetoc_1jiljio8k3">Introducción</h2>
<p>Enviar un mensaje cuando ocurre un evento en un servidor o software es un caso muy común que nos puede salvar de más de un aprieto. Existe como opción gestionarlo mediante <strong>envíos de correo</strong>, pero cuando se trata de muchos mensajes en poco tiempo podemos llegar rápidamente a los límites de envío de nuestro proveedor de correo.</p>
<p>Otra opción bastante popular (y que anteriormente utilizaba) es enviar el mensaje mediante un bot de <strong>Telegram</strong>. Sin embargo, aunque el proceso de crear un bot no tiene coste económico, es un tanto engorroso tener que crear uno por cada categoría para evitar tener todas las notificaciones mezcladas bajo la misma conversación. Además, se queda algo limitado para mi gusto a la hora de poder gestionar varios usuarios como destinatarios.</p>
<p>Finalmente, me decanté por <strong><a href="https://ntfy.sh/" target="_blank" rel="noopener noreferrer">ntfy</a></strong>, una solución de código abierto que permite ser alojada en nuestro servidor y que tiene clientes en <strong>Android</strong>, <strong>iOS</strong> y en <strong>navegador web</strong>. Y, como suele ser de costumbre, dispone de una imagen oficial de <strong>Docker</strong> para desplegar el servicio con mucha facilidad.</p>
<p> </p>
<h2 id="mcetoc_1jj7l4him2">Desplegando el servicio con Docker</h2>
<p>En primer lugar, creamos una carpeta donde almacenaremos tanto el archivo de compose.yaml de <strong>Docker</strong> como los archivos correspondientes al servicio. Dejo aquí uno de ejemplo:</p>
<pre class="language-yaml line-numbers"><code>services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command:
      - serve
    environment:
      TZ: Europe/Madrid
      NTFY_BASE_URL: https://subdominio.ejemplo.es
      NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
      NTFY_CACHE_DURATION: 72h
      NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_AUTH_USERS: 'usuario-ejemplo-admin:hash-ejemplo-admin:admin,usuario-ejemplo-lectura:hash-ejemplo-lectura:user'
      NTFY_AUTH_ACCESS: 'usuario-ejemplo-lectura:tema-solo-lectura:ro'
      NTFY_BEHIND_PROXY: true
      NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
      NTFY_ENABLE_LOGIN: true
      NTFY_REQUIRE_LOGIN: true
      NTFY_AUTH_TOKENS: 'usuario-ejemplo-admin:token:descripcion'

    volumes:
      - ./:/var/lib/ntfy
    ports:
      - 2586:80
    healthcheck:
        test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
        interval: 60s
        timeout: 10s
        retries: 3
        start_period: 40s
    restart: unless-stopped
    init: true
</code></pre>
<p>Como podemos ver, exponemos el puerto 80 del contenedor al puerto 2586 de nuestro host y los archivos de la aplicación al mismo directorio donde se encuentra el archivo de compose.</p>
<p>Explico en detalle en las diferentes configuraciones disponibles: </p>
<h3 id="mcetoc_1jjot1nol13">Zona horaria</h3>
<p>Con la variable <code>TZ</code> indicamos la zona horaria de la aplicación, para que se muestre la hora correcta de las notificaciones.</p>
<pre class="language-yaml line-numbers"><code>TZ: Europe/Madrid</code></pre>
<h3 id="mcetoc_1jjoubtag3s">Configuración </h3>
<p>Especificamos la URL donde se servirá la aplicación. Posteriormente, veremos un ejemplo de cómo hacerlo con <strong>Caddy</strong>. </p>
<pre class="language-yaml line-numbers"><code>NTFY_BASE_URL: https://subdominio.ejemplo.es</code></pre>
<p>Es importante también especificar que la aplicación va a ser servida detrás de un proxy inverso.</p>
<pre class="language-yaml line-numbers"><code>NTFY_BEHIND_PROXY: true</code></pre>
<h3 id="mcetoc_1jjoubtag3t">Caché</h3>
<p>La aplicación dispone de una base de datos <strong>SQLite</strong> que sirve como caché para almacenar los mensajes que no han podido ser enviados a todos los destinatarios (por ejemplo, si un dispositivo se encontraba apagado o sin conexión a internet) para poder transmitirlos a posteriori.</p>
<p>Podemos especificar la ruta con el siguiente parámetro, teniendo en cuenta que es la ruta del sistema de archivos del contenedor.</p>
<pre class="language-yaml line-numbers"><code>NTFY_CACHE_FILE: /var/lib/ntfy/cache.db</code></pre>
<p>También podemos indicar un tiempo máximo de duración de esta caché. En mi caso, la he limitado a 72 horas.</p>
<pre class="language-yaml line-numbers"><code>NTFY_CACHE_DURATION: 72h</code></pre>
<p>Por último, podemos establecer la ruta donde se almacenará la caché de los archivos adjuntos de los mensajes.</p>
<pre class="language-yaml line-numbers"><code>NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments</code></pre>
<h3 id="mcetoc_1jjp1jhth178">Haciendo nuestro servicio de acceso privado</h3>
<p>Dado que no queremos que cualquier persona pueda leer y enviar notificaciones con nuestro servicio, es necesario definir ciertas configuraciones.</p>
<p>Por defecto, queremos que el permiso de los usuarios sea denegado a todo a no ser que se indique lo contrario por usuario.</p>
<pre class="language-yaml line-numbers"><code>NTFY_AUTH_DEFAULT_ACCESS: deny-all</code></pre>
<p>También habilitaremos y requeriremos que el acceso a la aplicación se haga mediante autenticación.</p>
<pre class="language-yaml line-numbers"><code>NTFY_ENABLE_LOGIN: true
NTFY_REQUIRE_LOGIN: true</code></pre>
<h3 id="mcetoc_1jjp1jhth179">Configuración de usuarios</h3>
<p>Primero, indicamos dónde almacenar la base de datos <strong>SQLite</strong> de los usuarios en el sistema de archivos del contenedor.</p>
<pre class="language-yaml line-numbers"><code>NTFY_AUTH_FILE: /var/lib/ntfy/auth.db</code></pre>
<p>A continuación, tenemos que especificar la configuración de cada usuario, separado con comas, con la estructura <code>nombre_usuario:hash_contraseña:tipo_usuario</code>.</p>
<p>Para generar el hash de la contraseña, disponemos de un comando de la aplicación:</p>
<pre class="language-bash line-numbers"><code>sudo docker exec -it ntfy ntfy user hash </code></pre>
<p class="msg msg--warning">Importante a tener en cuenta: en el archivo compose.yaml hay que indicar el símbolo <code>$</code> dos veces respecto al hash generado por el comando.</p>
<p>Como tipo de usuario, podemos elegir entre <code>admin</code> o <code>user</code>. El de tipo <strong>admin</strong> tendrá acceso a todo con permisos de lectura y escritura, mientras que <strong>user</strong> sólo a lo que le especifiquemos manualmente.</p>
<p>Aquí podemos ver un ejemplo con un usuario de cada tipo:</p>
<pre class="language-yaml line-numbers"><code>NTFY_AUTH_USERS: 'usuario-ejemplo-admin:hash-ejemplo-admin:admin,usuario-ejemplo-lectura:hash-ejemplo-lectura:user'</code></pre>
<h3 id="mcetoc_1jjp1jhth17a">Configuración de permisos</h3>
<p>Los usuarios no administradores pueden tener cuatro tipos de permisos por tema. Los temas (topics) son la manera de categorizar las notificaciones en distintos canales.</p>
<ul>
<li>Lectura y Escritura (read-write)</li>
<li>Sólo lectura (read-only)</li>
<li>Sólo escritura (write-only)</li>
<li>Denegar (deny)</li>
</ul>
<p>En el siguiente ejemplo le daríamos al usuario <code>usuario-ejemplo-lectura</code> permisos de sólo lectura (<code>read-only</code>) al tema <code>tema-solo-lectura</code>. Podemos añadir más permisos separados por comas.</p>
<pre class="language-yaml line-numbers"><code>NTFY_AUTH_ACCESS: 'usuario-ejemplo-lectura:tema-solo-lectura:read-only'</code></pre>
<h3 id="mcetoc_1jjp1jhth17b">Tokens de autenticación</h3>
<p>Es posible autenticarnos mediante un token en vez de con una contraseña. Esto es una opción bastante recomendable por seguridad si vamos a integrarnos con otras aplicaciones, además de ser obligatorio con algunas de ellas ciertas.</p>
<p>Para generar un token aleatorio, disponemos del siguiente comando:</p>
<pre class="language-bash line-numbers"><code>sudo docker exec ntfy ntfy token generate</code></pre>
<p>En este ejemplo para el usuario <code>usuario-ejemplo-admin</code> indicaríamos un token (sustituyéndolo por el generado en el paso anterior) con una descripción para que sepamos identificarlo. Como en los casos anteriores, si necesitamos indicar más configuraciones, las podemos separar con comas.</p>
<pre class="language-yaml line-numbers"><code>NTFY_AUTH_TOKENS: 'usuario-ejemplo-admin:token:descripcion'</code></pre>
<p>Una vez configurado todo, levantamos el contenedor con docker compose.</p>
<pre class="language-bash line-numbers"><code>sudo docker compose up -d</code></pre>
<h2 id="mcetoc_1jjot1nol14">Configurando Caddy como reverse proxy</h2>
<p>En este ejemplo utilizaré <strong>Caddy</strong>, pero se podría utilizar cualquier otra solución de proxy inverso. Para más información sobre <strong>Caddy</strong>, recomiendo echar un vistazo al <a href="https://blog.adrianjg.es/sirve-tus-aplicaciones-web-con-https-de-manera-sencilla-con-caddy/">artículo. donde explico cómo configurarlo</a>.</p>
<p>Modificamos el archivo Caddyfile para añadir la configuración del sitio como proxy inverso, referenciando la red del host al puerto que configuramos en el archivo de compose de <strong>ntfy</strong>.</p>
<pre class="language-apacheconf line-numbers"><code>subdominio.ejemplo.es {
        reverse_proxy host.docker.internal:2586 
}
</code></pre>
<p> </p>
<h2 id="mcetoc_1jjp1jhth17c">Cliente Android</h2>
<p>Existe la opción de recibir mediante notificaciones web los avisos en nuestros dispositivos, accediendo a la URL donde hemos configurado la aplicación, pero es más práctico utilizar la aplicación nativa para <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy" target="_blank" rel="noopener noreferrer">Android</a>.</p>
<p>Existe también un cliente para <a href="https://apps.apple.com/us/app/ntfy/id1625396347" target="_blank" rel="noopener noreferrer">iOS</a>, aunque hay que tener en cuenta que, debido a una limitación en la ejecución de aplicaciones en segundo plano, es necesario realizar una <a href="https://docs.ntfy.sh/config/#ios-instant-notifications" target="_blank" rel="noopener noreferrer">configuración adicional para recibir las notificaciones de forma inmediata</a>.</p>
<p>Su configuración no puede ser más sencilla: primero, iniciamos sesión con uno de los usuarios previamente configurados. Después, pulsamos en el icono de más para suscribirnos a un tema. Por último, indicamos el nombre del tema, seleccionamos la opción de <em>Usar otro servidor</em> e indicamos la URL donde hemos publicado la aplicación.</p>
<figure class="post__image post__image--center align-center"><img loading="lazy"  src="https://blog.adrianjg.es/media/posts/10/suscripcion-tema-ntfy.jpg" alt="Pantalla de suscripción a un nuevo tema de ntfy" width="540" height="545" sizes="(max-width: 1920px) 100vw, 1920px" srcset="https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-xs.webp 640w ,https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-sm.webp 768w ,https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-md.webp 1024w ,https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-lg.webp 1366w ,https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-xl.webp 1600w ,https://blog.adrianjg.es/media/posts/10/responsive/suscripcion-tema-ntfy-2xl.webp 1920w"></figure>
<p> </p>
<h2 id="mcetoc_1jjot1nol15">Ejemplos de uso</h2>
<p>Para probar el funcionamiento de las notificaciones, nos podemos valer de <strong>curl</strong> para hacer una petición. El mensaje deberá aparecer en todos los clientes suscritos al tema <code>tema-ejemplo</code>.</p>
<pre class="language-bash line-numbers"><code>curl \
    -u 'usuario-ejemplo-admin:contraseña' \
    -d "¡Hola, mundo!" \
    https://subdominio.ejemplo.es/tema-ejemplo</code></pre>
<p> A continuación, indico unos ejemplos de uso:</p>
<ul>
<li>Alerta de acceso de SSH a nuestro servidor (<a href="https://docs.ntfy.sh/examples/#ssh-login-alerts" target="_blank" rel="noopener noreferrer">ejemplo de configuración</a>).</li>
<li>Alerta de falta de espacio en nuestro servidor (<a href="https://docs.ntfy.sh/examples/#low-disk-space-alerts" target="_blank" rel="noopener noreferrer">ejemplo de configuración</a>).</li>
<li>Integración con <strong><a href="https://uptime.kuma.pet/" target="_blank" rel="noopener noreferrer">Uptime Kuma</a></strong> para avisarnos cuando un sitio web se encuentra caído.</li>
<li>Integración con <strong><a href="https://crazymax.dev/diun/" target="_blank" rel="noopener noreferrer">Diun</a></strong> para recibir una notificación cuando exista una actualización de imagen de una de nuestras aplicaciones Docker.</li>
</ul>
<p>Además de las <a href="https://docs.ntfy.sh/integrations/" target="_blank" rel="noopener noreferrer">integraciones oficiales</a> listadas en la documentación de <strong>ntfy</strong>, podemos integrarlo fácilmente en nuestras propias aplicaciones o procesos, dado que simplemente nos es necesario hacer una petición POST de HTTP.</p>
<p>Desde que la descubrí, <strong>ntfy </strong>se ha convertido en una herramienta esencial para mantener un seguimiento de mis aplicaciones y servidores. Espero que con este artículo te hayan entrado ganas de probarla y que te aporte tanta utilidad como a mí. </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Sirve tus aplicaciones web con HTTPS de manera sencilla con Caddy</title>
        <author>
            <name>Adrián JG</name>
        </author>
        <link href="https://blog.adrianjg.es/sirve-tus-aplicaciones-web-con-https-de-manera-sencilla-con-caddy/"/>
        <id>https://blog.adrianjg.es/sirve-tus-aplicaciones-web-con-https-de-manera-sencilla-con-caddy/</id>
        <media:content url="https://blog.adrianjg.es/media/posts/9/hazel-z-FocSgUZ10JM-unsplash.jpg" medium="image" />
            <category term="Self-hosting"/>

        <updated>2026-02-25T22:15:45+01:00</updated>
            <summary type="html">
                <![CDATA[
                        <img src="https://blog.adrianjg.es/media/posts/9/hazel-z-FocSgUZ10JM-unsplash.jpg" alt="Imagen que representa una nube con aplicaciones" />
                    <p><strong>Caddy</strong> es un servidor web escrito en GO que se presenta como a una alternativa a tener en cuenta frente a <strong>nginx</strong> o <strong>Apache</strong>. En este artículo explicaré por qué lo estoy empezando a utilizar y ejemplos de uso.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <p><img src="https://blog.adrianjg.es/media/posts/9/hazel-z-FocSgUZ10JM-unsplash.jpg" class="type:primaryImage" alt="Imagen que representa una nube con aplicaciones" /></p>
                <p><strong>Caddy</strong> es un servidor web escrito en GO que se presenta como a una alternativa a tener en cuenta frente a <strong>nginx</strong> o <strong>Apache</strong>. En este artículo explicaré por qué lo estoy empezando a utilizar y ejemplos de uso.</p>

<p> </p>
<div class="post__toc">
<h3>Tabla de contenidos</h3>
<ul>
<li><a href="#mcetoc_1jibb64sgu">¿Por qué Caddy?</a></li>
<li><a href="#mcetoc_1jibb64sgv">Desplegando el servicio con Docker</a></li>
<li><a href="#mcetoc_1jibb7h6v13">Configurando Caddy</a>
<ul>
<li><a href="#mcetoc_1jidrrqbt49">Servir archivos estáticos</a></li>
<li><a href="#mcetoc_1jidrrqbt4a">Proxy inverso</a></li>
<li><a href="#mcetoc_1jidufihr6k">Servir una aplicación SPA (React, Vue, Angular)</a></li>
<li><a href="#mcetoc_1jidrrqbt4b">Redirección a www</a></li>
</ul>
</li>
</ul>
</div>
<p> </p>
<h2 id="mcetoc_1jibb64sgu">¿Por qué Caddy?</h2>
<p>Durante varios años he utilizado <span class="kY2IgmnCmOGjharHErah"><strong>nginx</strong> para servir mis proyectos web con bastante satisfacción, aunque a la hora de configurar la emisión de certificados con <strong>Let's Encrypt</strong> se me hacía bastante tedioso el proceso. Tras investigar qué alternativas existían para mi caso de uso, me encontré con <strong><a href="https://caddyserver.com/" target="_blank" rel="noopener noreferrer">Caddy</a></strong>.</span></p>
<p>Simplemente modificando el archivo de configuración y refrescando el servicio, se levantará tu sitio web con un certificado emitido por <strong>Let's Encrypt</strong> o <strong>ZeroSSL</strong>. También se encarga de forma transparente de renovar el certificado, sin necesidad de software ni configuración adicional.</p>
<p>Si a esto le sumamos un registro DNS wildcard en nuestro dominio, podremos desplegar de forma muy rápida los sitios web que necesitemos con HTTPS. En resumen, lo ideal para trastear con proyectos. Y lo mejor de todo, dispone de soporte oficial para funcionar con Docker.</p>
<h2 id="mcetoc_1jibb64sgv">Desplegando el servicio con Docker</h2>
<p>Creamos una carpeta para Caddy y en ella añadimos un archivo <strong>compose.yaml</strong> con este contenido:</p>
<pre class="language-yaml line-numbers"><code>services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./conf:/etc/caddy
      - ./srv:/srv
      - caddy_data:/data
      - caddy_config:/config

    extra_hosts:
      - host.docker.internal:host-gateway

volumes:
  caddy_data:
  caddy_config:</code></pre>
<p>En este archivo de configuración, específico para la versión 2 de Caddy (la actual en el momento en el que se ha escrito este artículo), exponemos los puertos 80 y 443 (TCP y UDP) del contenedor y mapeamos las carpetas necesarias para tener disponibles tanto los archivos de configuración como las carpetas para los archivos estáticos.</p>
<p>Un paso importante, con <code>host.docker.internal:host-gateway</code> habilitamos que el contenedor de Caddy tenga acceso a los puertos del servidor bajo el hostname de <code>host.docker.internal</code>.</p>
<p>Finalmente, levantamos el contenedor con docker compose.</p>
<pre class="language-bash line-numbers"><code>sudo docker compose up -d</code></pre>
<p> </p>
<h2 id="mcetoc_1jibb7h6v13">Configurando Caddy</h2>
<p>Otra de las ventajas que tiene Caddy es que la sintaxis para configurar sitios es muy sencilla. El archivo de configuración se encuentra en conf/Caddyfile y para recargar la configuración, sin necesidad de detener el contenedor, se puede utilizar este comando.</p>
<pre class="language-bash line-numbers"><code>sudo docker compose exec -w /etc/caddy caddy caddy reload</code></pre>
<h3 id="mcetoc_1jidrrqbt49">Servir archivos estáticos</h3>
<p>Con esta configuración, serviremos para el host <code>ejemplo.com</code> el contenido de la carpeta <code>srv/ejemplo</code> como archivos estáticos.</p>
<pre class="language-apacheconf line-numbers"><code>ejemplo.com {
        root * /srv/ejemplo
        file_server
}
</code></pre>
<h3 id="mcetoc_1jidrrqbt4a">Proxy inverso</h3>
<p>En este caso, servimos como proxy inverso el servicio o aplicación web del puerto <code>3000</code> del host.</p>
<pre class="language-apacheconf line-numbers"><code>ejemplo.com {
        reverse_proxy host.docker.internal:3000
}
</code></pre>
<h3 id="mcetoc_1jidufihr6k">Servir una aplicación SPA (React, Vue, Angular)</h3>
<p>Similar a la configuración de los archivos estáticos, le añadimos <code>encode</code> para transferir los archivos con compresión gzip y <code>try_files {path} /index.html</code> para delegar el enrutamiento a la aplicación single-page.</p>
<pre class="language-apacheconf line-numbers"><code>ejemplo.com {
        root * /srv/ejemplo
        file_server
        encode
        try_files {path} /index.html
}
</code></pre>
<h3 id="mcetoc_1jidrrqbt4b">Redirección a www</h3>
<p>Un caso común, donde se redirige un dominio base a su subdominio www.</p>
<pre class="language-apacheconf line-numbers"><code>ejemplo.com {
	redir https://www.{host}{uri}
}
</code></pre>
<p> </p>
<p>En la <a href="https://caddyserver.com/docs/caddyfile" target="_blank" rel="noopener noreferrer">documentación de Caddy</a> se pueden encontrar más ejemplos útiles, como configuraciones para servidor aplicaciones PHP, especificar páginas de error o añadir autenticación básica HTTP. </p>
<p>Personalmente, me he quedado encantado con este servidor web por su conveniencia, su amigable documentación y soporte con Docker. Si lo que ofrece te parece interesante, te animo a darle una oportunidad.</p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Primeros pasos al configurar un VPS</title>
        <author>
            <name>Adrián JG</name>
        </author>
        <link href="https://blog.adrianjg.es/primeros-pasos-al-configurar-un-vps/"/>
        <id>https://blog.adrianjg.es/primeros-pasos-al-configurar-un-vps/</id>
        <media:content url="https://blog.adrianjg.es/media/posts/8/debian-terminal.jpg" medium="image" />
            <category term="Self-hosting"/>

        <updated>2026-02-22T22:33:38+01:00</updated>
            <summary type="html">
                <![CDATA[
                        <img src="https://blog.adrianjg.es/media/posts/8/debian-terminal.jpg" alt="Una terminal de Debian" />
                    <p>Cada vez que doy de alta un servidor intento seguir unos pasos para asegurarme de que está todo listo para funcionar, aplicando unas medidas mínimas de seguridad y unas configuraciones a gusto. En este artículo los describiré uno a uno, con indicaciones específicas para Debian, mi distribución de confianza.</p>

                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                    <p><img src="https://blog.adrianjg.es/media/posts/8/debian-terminal.jpg" class="type:primaryImage" alt="Una terminal de Debian" /></p>
                <p>Cada vez que doy de alta un servidor intento seguir unos pasos para asegurarme de que está todo listo para funcionar, aplicando unas medidas mínimas de seguridad y unas configuraciones a gusto. En este artículo los describiré uno a uno, con indicaciones específicas para Debian, mi distribución de confianza.</p>

<p> </p>
<div class="post__toc">
<h3>Tabla de contenidos</h3>
<ul>
<li><a href="#mcetoc_1ji3je5ahb0">Crear un nuevo usuario y añadirlo al grupo root</a></li>
<li><a href="#mcetoc_1ji3je5ahb1">Deshabilitar el acceso por SSH con el usuario root</a></li>
<li><a href="#mcetoc_1ji3je5ahb2">Cambiar la zona horaria</a></li>
<li><a href="#mcetoc_1ji5lhpcfi2">Actualizar el software instalado</a></li>
<li><a href="#mcetoc_1ji5lhpcfi3">Instalar fail2ban para evitar ataques de fuerza bruta</a></li>
<li><a href="#mcetoc_1ji5lhpcfi4">Limitar los puertos de entrada con un cortafuegos</a></li>
<li><a href="#mcetoc_1ji5lhpcfi5">Configurar swap en servidores con RAM limitada</a></li>
<li><a href="#mcetoc_1ji64gnafh">Cambiar el hostname</a></li>
</ul>
</div>
<p> </p>
<h2 id="mcetoc_1ji3je5ahb0">Crear un nuevo usuario y añadirlo al grupo root</h2>
<p>Una vez tengo el acceso por SSH al servidor con el usuario root, lo primero que hago es crear un nuevo usuario. Supongamos que lo llamamos usuario.</p>
<pre class="language-bash line-numbers"><code>sudo adduser usuario</code></pre>
<p>A continuación, añado el usuario al grupo <strong>sudo</strong> para que pueda ejecutar permisos elevados con sudo y así quitarnos la dependencia con el usuario root.</p>
<pre class="language-bash line-numbers"><code>sudo adduser usuario sudo</code></pre>
<p> </p>
<h2 id="mcetoc_1ji3je5ahb1">Deshabilitar el acceso por SSH con el usuario root</h2>
<p>Entro de nuevo al servidor por SSH, esta vez con el nuevo usuario. Accedo a la configuración del <strong>daemon de SSH</strong> con el propósito de deshabilitar el acceso con el usuario root.</p>
<pre class="language-bash line-numbers"><code>sudo nano /etc/ssh/sshd_config</code></pre>
<p>Localizo la línea comentada de <code>PermitRootLogin no</code> y la descomento. En el caso de que no exista, la añado al archivo. Una vez hecho esto, guardo los cambios.</p>
<p>Por último, para que las modificaciones surjan efecto, reinicio el servicio de <strong>sshd</strong>.</p>
<pre class="language-bash line-numbers"><code>sudo systemctl restart sshd</code></pre>
<p class="msg msg--warning">Importante configurar esto una vez nos aseguremos de que nuestro usuario creado previamente tenga los permisos de sudo. Por ello, insisto en realizar este cambio con el nuevo usuario.</p>
<p> </p>
<h2 id="mcetoc_1ji3je5ahb2">Cambiar la zona horaria</h2>
<p>No hay nada que descuadre más que tener un servidor con una zona horaria distinta a la que vivo, en especial cuando consulto logs. Para remediarlo, ejecuto el siguiente comando para poner el correspondiente a la península de España.</p>
<pre class="language-bash line-numbers"><code>sudo timedatectl set-timezone Europe/Madrid</code></pre>
<p>Si queremos poner el de otra zona, podemos consultar las disponibles con el comando <code>timedatectl list-timezones</code> y sustituir del ejemplo <code>Europe/Madrid</code> por la zona horaria que nos interese. </p>
<p> </p>
<h2 id="mcetoc_1ji5lhpcfi2">Actualizar el software instalado</h2>
<p>Las imágenes de instalación de nuestro proveedor de VPS no tienen por qué estar actualizadas con frecuencia, por lo que no es mala idea realizar una actualización de los paquetes disponibles para tener las versiones más recientes y seguras del software.</p>
<p>Primero, se actualizan los repositorios.</p>
<pre class="language-bash line-numbers"><code>sudo apt-get update</code></pre>
<p>Y luego, actualizo los paquetes.</p>
<pre class="language-bash line-numbers"><code>sudo apt-get upgrade</code></pre>
<p>Adicionalmente, puede que exista la posibilidad de que la imagen de la distro instalada no sea la de la versión más reciente y queramos actualizar. Por ejemplo, a la hora de escribir este artículo, varios proveedores ofrecen una imagen para Debian 12, pero no para la recientemente lanzada Debian 13.</p>
<p>Para actualizar la distro a una nueva versión, es recomendable acceder a la documentación de la versión y seguir los pasos detenidamente. En el caso de actualizar Debian 12 a la versión 13, se puede consultar desde <a href="https://www.debian.org/releases/trixie/release-notes/upgrading.es.html" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
<p class="msg msg--warning">Existe cierto riesgo de que la versión de la distro no sea soportada por el sistema de virtualización del proveedor del VPS, pero qué mejor momento para descubrirlo que con un servidor recién instalado. </p>
<p> </p>
<h2 id="mcetoc_1ji5lhpcfi3">Instalar fail2ban para evitar ataques de fuerza bruta</h2>
<p>Tener un servidor abierto a Internet hace se encuentre expuesto ante ataques de bots que acribillan el servicio de SSH con ataques de fuerza bruta para hacerse con el control de la máquina.</p>
<p>Una opción disponible que muchos recomiendan es cambiar el puerto de SSH por otro distinto. Pero, en mi opinión, aunque mitiga una buena parte de los ataques automatizados, no me parece que aporte una seguridad suficiente.</p>
<p>Por suerte, existe <strong>fail2ban, </strong>un software muy útil que nos permite limitar los intentos de acceso que se hacen a diferentes servicios y banearlos temporalmente.</p>
<p>Se encuentra disponible para instalar directamente desde los repositorios de Debian.</p>
<pre class="language-bash line-numbers"><code>sudo apt-get install fail2ban</code></pre>
<p>A continuación, genero un archivo de configuración por separado para tener la configuración más ordenada, sin tocar los archivos por defecto.</p>
<pre class="language-bash line-numbers"><code>sudo nano /etc/fail2ban/jail.local</code></pre>
<p>Añado al archivo la siguiente configuración:</p>
<pre class="language-bash line-numbers"><code>[DEFAULT]
bantime = 24h
findtime = 10m
maxretry = 5

[sshd]
enabled = true</code></pre>
<p>Con <code>bantime</code> se define el tiempo en el que se bloqueará el acceso tras infringir el número de intentos, indicado en <code>maxretry</code>, en un periodo de tiempo determinado en <code>findtime</code>. Por ejemplo, en la configuración aplicada, si existen 5 intentos fallidos de acceso en un periodo de 10 minutos, la dirección IP se bloqueará durante 24 horas.</p>
<p>Reiniciamos el servicio para aplicar los cambios.</p>
<pre class="language-bash line-numbers"><code>sudo systemctl restart fail2ban</code></pre>
<p>Por último, podemos consultar el estado de la jaula para sshd con <strong>fail2ban-client</strong>, el cual nos indica el número total de bloqueos y los actuales. </p>
<pre class="language-bash line-numbers"><code>sudo fail2ban-client status sshd</code></pre>
<p> </p>
<h2 id="mcetoc_1ji5lhpcfi4">Limitar los puertos de entrada con un cortafuegos</h2>
<p>La gran mayoría de proveedores disponen de un panel de control para poder configurar las políticas del cortafuegos del VPS. Siempre lo aprovecho para limitar el tráfico de entrada a los servicios mínimos posibles.</p>
<p>Generalmente me basta con la siguiente configuración:</p>
<ul>
<li>Habilitar los puertos TCP 80 (<strong>HTTP</strong>) y 443 (<strong>HTTPS</strong>) para los servicios web.</li>
<li>Habilitar el puerto TCP 23 para el acceso por <strong>SSH</strong>.</li>
<li>Denegar el resto de puertos.</li>
</ul>
<p>En el caso de que nuestro proveedor no ofrezca dicha opción, se puede de forma alternativa configurar con <strong>ufw </strong>un cortafuegos a nivel de software.</p>
<p> </p>
<h2 id="mcetoc_1ji5lhpcfi5">Configurar swap en servidores con RAM limitada</h2>
<p>En el caso de disponer un VPS con RAM limitada, no es mala idea añadir <strong>swap </strong>para que el servidor pueda disponer de margen para paginar la memoria en casos puntuales, en especial si nos lo permite el espacio en disco disponible.</p>
<p>En primer lugar, compruebo ya hay disponible una swap pre-configurada, dado que puede variar dependiendo de la imagen aportada por el proveedor.</p>
<pre class="language-bash line-numbers"><code>free -h</code></pre>
<p>Si la línea de Swap indica que tiene asignados <code>0B</code> de total, significa que no hay swap configurada. De lo contrario, ¡enhorabuena! Te puedes ahorrar este paso.</p>
<p>Ejecuto el siguiente comando para reservar el espacio en disco con el archivo de swap. En mi caso, con un 1GB de RAM, suelo reservar otro GB para la swap. </p>
<pre class="language-bash line-numbers"><code>sudo fallocate -l 1G /swapfile</code></pre>
<p>Asigno permisos al archivo para limitar el acceso sólo a usuarios con permisos de root, para mayor seguridad.</p>
<pre class="language-bash line-numbers"><code>sudo chmod 600 /swapfile</code></pre>
<p>Formateo el archivo para usarlo como swap.</p>
<pre class="language-bash line-numbers"><code>sudo mkswap /swapfile</code></pre>
<p>Y con el siguiente comando lo habilito para que se use como swap.</p>
<pre class="language-bash line-numbers"><code>sudo swapon /swapfile</code></pre>
<p>Por último, se necesita persistir esta configuración en el caso de que exista un reinicio del servidor. Para ello, modifico el archivo /etc/fstab y añado la siguiente línea.</p>
<pre class="language-bash line-numbers"><code>/swapfile none swap sw 0 0</code></pre>
<p> </p>
<h2 id="mcetoc_1ji64gnafh">Cambiar el hostname</h2>
<p>Como guinda del pastel, qué mejor que poner un buen nombre al servidor recién configurado. Por ejemplo, si queremos el poco original nombre de <strong>miservidor</strong>, introducimos el siguiente comando.</p>
<pre class="language-bash line-numbers"><code>sudo hostnamectl set-hostname miservidor</code></pre>
<p> </p>
<p>Y aquí terminan mis pasos esenciales para ir empezando a funcionar nuestro nuevo servidor. Espero que os hayan sido de utilidad. Ahora con todo listo, ¡a empezar a trastear! </p>
            ]]>
        </content>
    </entry>
</feed>
