Ejecutar tu propio Progress Watch

El servidor es de código abierto bajo la AGPL, y el servicio alojado ejecuta la misma imagen que ejecutarías tú. Alojarlo tú mismo cuesta un contenedor y un Redis, y la base de datos se mantiene pequeña para siempre porque el progreso nunca se escribe en ella.

Un solo archivo

docker-compose.yml es toda la instalación. Ejecuta la imagen publicada, así que no hay nada que clonar ni nada que compilar: cópialo de la página de Docker, que imprime el archivo actual, sustituye el secreto de ejemplo por openssl rand -hex 64 y arráncalo:

Eso es toda la instalación: la aplicación en http://localhost:7979, un Redis y un archivo SQLite en un volumen con nombre. Ábrela, crea un espacio, y el menú Conectar de ese espacio te da los comandos con su UUID ya puesto.

Configurarlo

El archivo es tuyo desde el momento en que lo guardas, y es el único sitio donde se configura nada: cada ajuste es una línea de un bloque environment:, así que añadir uno es añadir una línea:

    environment:
      - SECRET_KEY_BASE=8f3c…
      - DATABASE_URL=postgresql://progresswatch:secret@db.internal/progresswatch
      - VAPID_PUBLIC_KEY=BJ1w…
      - VAPID_PRIVATE_KEY=xK2p…

Variables de entorno es la lista completa, y todo lo que hay en ella funciona aquí. Para publicarlo en un puerto distinto del 7979, cambia el lado izquierdo de 7979:3000: la aplicación dentro del contenedor siempre está en el 3000.

curl http://localhost:7979/up

{"status":"ok","database":true,"redis":true,"worker":true}

Qué está corriendo

Dos procesos y un archivo. La aplicación responde a las peticiones y ejecuta el worker dentro de sí misma — Sidekiq va embebido en Puma, así que a una notificación no le puede faltar un servicio propio. Redis guarda el progreso actual de cada tarea y la cola de trabajos; SQLite guarda los espacios y la forma de cada tarea.

Actualizarlo

Nombra el servicio. Un docker compose pull a secas también descarga Redis, y si se ha publicado un redis:8-alpine más reciente, up -d reemplaza también ese contenedor — y tira el progreso de todo lo que esté corriendo.

docker compose pull progresswatch
docker compose up -d progresswatch

Reemplazar solo el contenedor de la aplicación deja cada tarea en curso justo donde estaba, porque el progreso vive en Redis y a Redis no se le tocó. Los títulos y la estructura están en el volumen y sobreviven de todos modos. Nada sobrevive a docker compose down ni a un reinicio del host: Redis no guarda nada en disco, a propósito.

Por qué el 7979

No el 3000. Este es un servicio que dejas corriendo durante semanas mientras trabajas, y el 3000 es donde aterrizan tus propios servidores de desarrollo: Rails, Vite, Next, Grafana y la mitad de las líneas docker run de otras guías. Un servicio en segundo plano no debería ocupar el puerto más disputado de la máquina, y menos aún uno cuya URL pegas en un secreto de CI y luego olvidas.

Dentro del contenedor sigue siendo el 3000; solo se movió el puerto publicado, y eso es la línea 7979:3000.

Perder el volumen es recuperable

En él están los espacios y la estructura de las tareas, y nada más. Si desaparece, creas un espacio nuevo y tus procesos lo repueblan todo en su siguiente petición. Haz copia de seguridad si prefieres no repartir un UUID de espacio nuevo a todo lo que reporta ahí, pero no hay nada en él que no se pueda reconstruir ejecutando los trabajos otra vez.

SQLite o PostgreSQL

Los dos, desde la misma imagen y las mismas migraciones, elegido con DATABASE_URL. SQLite es lo predeterminado y es la respuesta correcta para una sola máquina: dos tablas, búsquedas por clave primaria, casi nunca escrituras. Apunta a postgresql://… cuando ejecutes varios contenedores de la aplicación, porque no pueden compartir un archivo.

Detrás de un proxy

Pon FORCE_SSL=true en cuanto algo delante de la aplicación termine el TLS. Está desactivado por defecto para que una instalación a la que se llega por HTTP simple no te redirija a un certificado que no tienes.

Notificaciones

Pon un par de claves VAPID y aparecerá un botón «Avisarme» en cada espacio; deja las claves sin poner y la función no existe. No hay nada que registrar con Apple ni con Google. Mira notificaciones para la historia completa, incluido lo que tiene que pasar en un teléfono.

Barrer los espacios vacíos

Abrir el sitio crea un espacio para ti, así que se crean muchos que nunca se usan, incluidos los de cualquier cosa que rastree la página. Un espacio que alguna vez haya tenido una tarea se conserva para siempre, por mucho que nadie lo mire. Solo se barren los que nunca tuvieron ni una tarea, y solo cuando tienen más de 30 días.

Nada lo ejecuta por ti. Prográmalo como ya programes las cosas:

# crontab, a diario a las 04:17
17 4 * * * cd /path/to/progresswatch && docker compose exec -T progresswatch bin/rails sweep_empty_spaces

Como nada corre con temporizador, 30 días es un mínimo y no un calendario: ejecútalo a diario y los espacios vacíos se van el día en que califican; ejecútalo una vez al año y se quedan hasta entonces. Saltárselo no pone en riesgo tus datos — el barrido solo toca filas que nunca se usaron — simplemente significa que la tabla crece con espacios que nadie abrió jamás.

Tu instancia no es indexable

Toda página de una instalación propia es noindex, robots.txt lo prohíbe todo y /sitemap.xml responde 404. Desde la máquina de alguien no hay público al que llegar ni nada allí que quiera encontrarse en una búsqueda, y no hay que configurar nada para ello: es lo que hay.