GitHub No Es Tu Respaldo. Una Cuenta Suspendida Lo Demostró Esta Semana.
Un desarrollador perdió 104 repos esta semana. Yo ya había dejado de confiar en GitHub con los míos.
Esta semana un desarrollador descubrió a las 9 de la mañana que sus 104 repos de GitHub ya no existían. Sin hack. Sin bug. Sin disco duro que se muriera. Solo un email automatizado: cuenta suspendida, reevaluamos tu Student Pack de 2019, ahora creemos que no eras elegible. Seis años después. 24 de esos repos no tenían backup en ningún otro lado. Desaparecidos por una decisión algorítmica tomada un martes por la mañana.
TLDR: Si estás en la misma situación (todo en GitHub, sin mirror, sin plan B) estás a un clasificador de distancia del mismo email a las 9am. Hay una solución. No cuesta nada, corre en un servidor que probablemente ya alquilas, y casi nadie se molesta. La pregunta es por qué.

Las respuestas bajo ese post son la otra historia. Cientos de devs dándose cuenta en tiempo real de que están exactamente en la misma situación. Todo en GitHub. Sin mirror. Sin plan B. Y la misma pregunta volviendo en cada hilo: ok pero concretamente, ¿qué hago ahora?
Yo no esperé. No por paranoia, no porque tuviera una bola de cristal. Solo porque en algún punto de tu carrera como dev dejas de confundir "alojado en otro lado" con "respaldado". GitHub es una gran herramienta. Es un gran forge. Es una gran red social para código. No es un backup, ese nunca ha sido su trabajo, y está escrito en blanco y negro en sus términos de servicio.
104 Repos. Una Decisión Automatizada. Sin Aviso.
La historia que circuló esta semana es lo suficientemente simple para contarla en dos oraciones. Un desarrollador se registró para un Student Pack gratuito en 2019. Seis años después, una revisión automatizada reevaluó la elegibilidad original, decidió retroactivamente que nunca fue legítima, y suspendió la cuenta. 104 repos ocultos. 24 de ellos nunca pusheados a ningún otro lado.
No importa si el reclamo original del Student Pack era legítimo o no. Lo que importa es que años de trabajo pueden desaparecer detrás de un proceso en el que no tienes voz, en una línea de tiempo que no puedes predecir, disparado por un clasificador de 2026 recalificando un formulario que llenó un pibe de 19 años en 2019.
Los hilos bajo el post están llenos de gente haciendo las cuentas en público. Tengo 60 repos. He estado en GitHub desde 2014. Nunca pensé en un mirror. Los que están confiados son los que tienen git auto-alojado corriendo en algún lado. No hay muchos de ellos.
No recibes un aviso antes de que esto te pase.
Lo Que GitHub Realmente Te Promete (Es Menos de Lo Que Piensas)
GitHub no promete mantener tus repos. Prometen correr una plataforma.
Lee los Términos de Servicio sobrio (es un bodrio, hazlo una vez). Las cláusulas relevantes no están ocultas. GitHub puede suspender o terminar cuentas a su discreción. Tu contenido puede ser removido si viola políticas, incluyendo políticas que no existían cuando creaste la cuenta. Y tu obligación como usuario es mantener tus propias copias de cualquier cosa que no puedas permitirte perder.
Incluso hay un nombre para esto. La industria cloud lo ha estado usando por más de una década y cada proveedor mayor tiene una página sobre ello: el Modelo de Responsabilidad Compartida. El proveedor corre la plataforma. El cliente es dueño de los datos. GitHub no lo pone en la página principal porque nadie se registraría si dijera "tus datos son tu problema", pero el contrato es el mismo.
El error que casi todos cometen es uno de categoría. Tratamos a GitHub como un filesystem. Se ve como uno (carpetas, archivos, historial). Se siente como uno (siempre ahí, siempre sincronizado). Pero es un servicio administrado con un TOS, y los servicios administrados tienen una puerta de salida operada por el proveedor.
No estoy argumentando contra el contrato. Solo lo estoy leyendo. Una vez que lo has leído, no puedes des-leerlo.
No Espero Incidentes. Ese Es un Principio de Diseño.
Tengo una regla para cualquier tercero del que dependo: si perderlo dolería, tiene una copia local. No porque espere que el proveedor falle. Porque el costo de estar equivocado sobre eso es demasiado alto.
Esto es higiene estándar de infra, no paranoia. No discutes con el DBA sobre si el primario "podría" caerse. Configuras la réplica porque así es como construyes infra que sobrevive un martes.
El mismo principio es por lo que reconstruí toda mi configuración de agentes AI la semana que Anthropic mató mi setup de OpenClaw de $200/mes y me obligó a reconstruirlo por $15. No esperé a que el anuncio me mordiera dos veces. En el momento que un vendor cambia las reglas unilateralmente, la reacción correcta no es renegociar. Es ser dueño de la siguiente versión.
La gente de seguridad llama a esto seguridad por diseño. Las decisiones que tomas en tiempo de arquitectura son decisiones que no tienes que tomar bajo estrés. No diseñas una salida de emergencia durante el incendio. No escribes una estrategia de backup a las 9am mientras miras un email de suspensión y un café que se enfrió.
Así que tenía un mirror. Antes de todo esto. Por una razón: mi código es el único entregable que no puedo recrear. Servidores puedo reconstruir. Configs puedo reescribir. Seis años de commits en 39 repos privados, ese no puedo.
El mirror existe para que nunca tenga que escribir un post de blog de domingo por la noche titulado Cómo Me Recuperé de una Suspensión de GitHub.
Mi Setup: Mirror de Forgejo Detrás de una Mesh NetBird

Dos piezas. Ese es todo el setup.
Forgejo es un git forge auto-alojado. Se bifurcó de Gitea en 2022 cuando Gitea se movió a una estructura de empresa con fines de lucro (sí, este es el tipo de detalle que importa una vez que empiezas a preocuparte por quién es dueño de tus herramientas). Corre en un solo contenedor con SQLite. Sin cluster PostgreSQL, sin Redis, sin microservicios. Habla el protocolo git nativamente, sin abstracción de capa web. Si clonaste un repo de Forgejo, no notarías que no estás en GitHub. La misma lógica por la que argumenté en por qué los CLIs vencen a MCP para agentes AI: la primitiva vence al wrapper, siempre.
NetBird es una mesh basada en WireGuard. Mi laptop, mi VPS y un par de otros dispositivos están en una red privada con IPs privadas. Sin exposición pública. Sin reverse proxy. Sin certificado TLS que renovar. Si no estás en la mesh, el puerto ni siquiera responde.
El contenedor de Forgejo se ve así:
services:
forgejo:
image: codeberg.org/forgejo/forgejo:11
container_name: forgejo
restart: unless-stopped
ports:
- "100.69.51.147:3000:3000"
volumes:
- forgejo_data:/data
environment:
- FORGEJO__server__ROOT_URL=http://forgejo.mesh:3000
- FORGEJO__server__DISABLE_SSH=true
- FORGEJO__service__DISABLE_REGISTRATION=true
- FORGEJO__mirror__DEFAULT_INTERVAL=8h
Cuatro decisiones a destacar:
El puerto se bindea solo a la IP de mesh (100.69.51.147). No 0.0.0.0. No expuesto al internet público. El mirror es un recurso privado que vive detrás de la misma cerca que mis otros servicios internos.
SSH está deshabilitado. Nunca pusheo al mirror. Es solo lectura. Deshabilitar SSH remueve toda una superficie de ataque que no necesito.
El registro está deshabilitado. Instancia de un solo usuario. Sin formulario de registro para que algún bot encuentre un martes.
El intervalo de sincronización del mirror es 8 horas. Forgejo tiene soporte nativo de pull mirror: le das una URL de GitHub y un PAT, y pullea cada 8 horas para siempre. Sin cron, sin script, sin webhook. El forge lo hace él mismo.
Luego un pequeño script registra todos mis repos de GitHub como mirrors vía la API de Forgejo. Es idempotente: lista los repos, verifica cuáles ya tienen un mirror, y crea solo los faltantes. Córrelo una vez en la instalación. Córrelo de nuevo cada vez que crees un nuevo repo de GitHub. O prográmalo semanalmente, tú decides. La única llamada API por repo se ve así:
curl -X POST "$FORGEJO_URL/api/v1/repos/migrate" \
-H "Authorization: token $FORGEJO_TOKEN" \
-d '{
"clone_addr": "https://github.com/myorg/repo.git",
"repo_name": "repo",
"mirror": true,
"private": true,
"auth_token": "'$GITHUB_PAT'",
"service": "github"
}'
Envuelve eso en un loop sobre gh repo list myorg, con un check sobre si el mirror ya existe, y terminaste. El PAT y el token de Forgejo vienen de un administrador de secretos auto-alojado en runtime, nunca en disco.
Huella total de recursos: cerca de 100MB de RAM, 2GB de disco para 39 repos. El contenedor reinicia en dos segundos. La sincronización de 8h corre en background y me olvido que existe por semanas.
Qué Cubre Esto, y Qué No
Esta es la parte que la mayoría de artículos de "auto-aloja tu git" se saltean. Un pull mirror no es un backup completo de GitHub.
Lo que el mirror salva es el lado git de las cosas: cada commit, cada branch, cada tag, historial completo a través de todas las ramas. Submodules y LFS también funcionan si das el paso extra de configurarlos, y deberías si los usas.
Lo que el mirror NO salva es todo lo que vive fuera del protocolo git. Issues. Pull requests y comentarios de review. Páginas de Wiki. Logs de ejecución de Actions (los archivos YAML sí, el historial de ejecución no). Configuraciones de repo, webhooks, deploy keys, listas de acceso de colaboradores. Todo eso es metadata específico de GitHub, almacenado en su base de datos, no en tu directorio .git. Si GitHub desaparece mañana, mis 39 repos están intactos, hasta 8 horas desactualizados. Mis issues y PRs no.
Para mi caso de uso (repos privados, trabajo mayormente solo, código de infraestructura) ese es un trade aceptable. Para un equipo más grande corriendo la mitad de su workflow dentro de GitHub Issues, la conversación es diferente. Querrías la herramienta oficial de backup de repos de GitHub, o un tercero que pegue a la API para issues y PRs también, además del git mirror.
También está el caso de yo borrando un repo en GitHub por accidente. El pull mirror nota que el upstream se fue, pero Forgejo no auto-borra la copia local. El último estado sincronizado se queda. Eso es en realidad una feature: una acción destructiva upstream no se propaga. (No voy a reclamar que diseñé esto a propósito. Lo noté la primera vez que limpié una org vieja y vi el mirror todavía sentado ahí un mes después. Red de seguridad gratis, lo mantuve.)
Sabe qué hace tu filtro y qué no. No te vendas una historia de backup que no coincide con el contrato que realmente tienes con tus herramientas.
No Tienes Que Esperar Tu Propio Incidente
La historia de los 104 repos no es excepcional. Solo es visible. Lo mismo pasa cada semana a gente cuya audiencia es demasiado pequeña para que el post viaje. Suspensiones de cuenta, takedowns DMCA equivocados, disputas de facturación, falsos positivos de clasificadores, método de pago expirado en un país que el sistema de facturación de GitHub maneja raro. La lista es larga. La solución es la misma en cada caso.
En seis meses, GitHub publicará un post de blog sobre "mejorando cómo comunicamos acciones de cuenta". Habrá un nuevo dashboard, un FAQ refrescado, una página de estado más bonita. Nadie la leerá antes de la siguiente ola.
Mientras tanto los devs que shippean seguirán shippeando. Con un mirror. En su propia infra. Alcanzable cuando GitHub está caído, cuando un clasificador falla, cuando un Student Pack de 2019 de repente se vuelve un problema de 2026. No mucho. Solo un docker-compose y tres horas de config un domingo.
Git es distribuido por diseño. Nosotros somos los que decidimos olvidar.
Fuentes
- Documentación de Forgejo: https://forgejo.org/docs/latest/
- NetBird (mesh WireGuard): https://netbird.io/
- AWS Shared Responsibility Model (el framing original de quién es dueño de qué): https://aws.amazon.com/compliance/shared-responsibility-model/
(*) La portada es generada por AI. Ningún repo real de GitHub fue dañado en la creación de esta imagen.