Saltar al contenido
ES EN
Tema: automático

Laboratorio · Almacenamiento

Monta copias de seguridad con BorgBackup y borgmatic en Docker Compose

Cifra tus copias con BorgBackup, automatízalas con borgmatic en Docker Compose y réplica externa por SSH en modo append-only, con comprobaciones y restauración.

Laboratorio · Almacenamiento

60 min nivel medio configuración validada

Arquitectura del laboratorioComponentes: Datos a copiar, borgmatic + Borg, Repositorio local, Réplica SSH, Repositorio remoto, Tú (terminal).lecturaborg createSSHappend-onlyextract / checkDatos a copiar: paso 1Datos a copiarcarpeta ./datos, solo l…Tú (terminal): paso 4Tú (terminal)docker compose execborgmatic + Borg: paso 3borgmatic + Borgcontenedor con cronRepositorio local: paso 3Repositorio local./repositorio, cifradoRéplica SSH: paso 2Réplica SSHOpenSSH + borg serveRepositorio remoto: paso 2Repositorio remotovolumen append-only
  1. Datos a copiar · paso 1 carpeta ./datos, solo lectura
  2. borgmatic + Borg · paso 3 contenedor con cron
  3. Repositorio local · paso 3 ./repositorio, cifrado
  4. Réplica SSH · paso 2 OpenSSH + borg serve
  5. Repositorio remoto · paso 2 volumen append-only
  6. Tú (terminal) · paso 4 docker compose exec

Qué vas a montar y por qué

Vas a montar la copia de seguridad de un servidor doméstico con BorgBackup y borgmatic dentro de Docker Compose: un repositorio local cifrado, retención automática de archivos antiguos, una réplica externa por SSH en modo append-only, comprobaciones periódicas y una prueba de restauración real.

La idea encaja con la regla 3-2-1: los datos originales, una copia local cifrada y otra copia en otra ubicación. El servidor remoto de este laboratorio es un contenedor con OpenSSH que ejecuta borg serve. En producción lo normal es que ese servidor sea otra máquina tuya, un NAS en casa de un familiar o un proveedor compatible con Borg; aquí lo levantamos en contenedor para entender el esquema completo.

El servicio principal es borgmatic (imagen de la comunidad borgmatic-collective, fijada a la 2.1.10, una versión anterior a la última publicada a propósito). El contenedor de réplica es una dependencia que construimos con un Dockerfile mínimo sobre Alpine. Sustituye todos los valores CHANGE_ME_* por los tuyos y no los subas a ningún repositorio.

Laboratorio: Monta copias de seguridad con BorgBackup y borgmatic en Docker Compose

Requisitos

SistemaLinux (Debian 12 / Ubuntu 24.04)
Docker Engine28.5.2
borgmatic (imagen docker-borgmatic, incluye Borg)2.1.10
Alpine Linux (imagen base de la réplica)3.24.4
OpenSSH (servidor de la réplica)7.2 o superior
RAM2 GB
Disco20 GB

Paso a paso

  1. Prepara la estructura y las claves SSH

    Primero creamos las carpetas y una clave SSH dedicada a las copias. Usar una clave exclusiva permite restringirla en el servidor a ejecutar únicamente borg serve, sin shell interactiva.

    La clave se genera sin passphrase porque la usará un proceso automático; su protección está en que en el servidor solo puede lanzar Borg. El cifrado de los datos lo da la passphrase del repositorio, que es otra cosa distinta.

    Copia ahora el contenido de ssh-cliente/id_ed25519.pub (lo necesitarás en el paso 2) antes de cambiar los propietarios. El contenedor de borgmatic se ejecuta como root y OpenSSH exige que la clave privada pertenezca al usuario que la usa y no sea accesible a otros, así que pasamos a root solo la clave privada; la carpeta queda legible para tu usuario y la pública sigue siendo legible por todos.

    mkdir -p borg-lab/datos borg-lab/repositorio borg-lab/ssh-cliente borg-lab/replica borg-lab/borgmatic
    cd borg-lab
    ssh-keygen -t ed25519 -N "" -C borgmatic-lab -f ssh-cliente/id_ed25519
    cat ssh-cliente/id_ed25519.pub
    sudo chown 0:0 ssh-cliente/id_ed25519
    sudo chmod 600 ssh-cliente/id_ed25519
    echo "contenido de prueba" > datos/prueba.txt
  2. Configura la réplica externa por SSH

    La réplica es un contenedor Alpine con OpenSSH y BorgBackup. La clave pública del cliente se autoriza con un comando forzado: borg serve --restrict-to-path. Según la documentación de borg serve, --restrict-to-path limita el acceso a esa ruta; la opción restrict de OpenSSH (disponible desde la 7.2) bloquea funciones peligrosas como el reenvío de puertos.

    Para no depender de si un servidor en modo append-only permite inicializar un repositorio, el primer arranque usa el comando forzado sin --append-only; el repositorio remoto se crea en el paso 3 y justo después activamos --append-only y recreamos la réplica.

    Ojo con lo que garantizará ese modo: la documentación aclara que append-only afecta a la estructura de bajo nivel del repositorio, y que delete, prune y la lectura siguen permitidos. Es una barrera contra borrados accidentales, no una inmutabilidad total.

    Evitamos contraseñas: solo clave pública (sshd_config usa el formato clave valor de OpenSSH, por eso el fichero se declara como text). Sustituye CHANGE_ME_PUBLIC_KEY por el contenido completo de id_ed25519.pub, la línea que empieza por ssh-ed25519. El entrypoint genera las claves de host en un volumen y las deja con los permisos que sshd exige.

    La cuenta borg se crea con adduser -D y se desbloquea con sed sobre /etc/shadow (sustituye el campo de contraseña bloqueada ! por *), sin depender de opciones de passwd; así sshd admite el login por clave. El puerto SSH de la réplica solo se publica en 127.0.0.1:2222; borgmatic accede por la red interna de Compose.

    Fichero replica/sshd_config

    Port 22
    HostKey /etc/ssh/hostkeys/ssh_host_ed25519_key
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    PermitRootLogin no
    AllowUsers borg
    AuthorizedKeysFile /etc/ssh/authorized_keys/%u
    ClientAliveInterval 10
    ClientAliveCountMax 30

    Fichero replica/authorized_keys

    command="borg serve --restrict-to-path /srv/borg",restrict CHANGE_ME_PUBLIC_KEY

    Sustituye cada valor CHANGE_ME_… por uno tuyo antes de arrancar: no dejes contraseñas ni claves de ejemplo.

    Fichero replica/Dockerfile

    FROM alpine:3.24.4
    
    RUN apk add --no-cache borgbackup openssh-server \
     && adduser -D -s /bin/sh borg \
     && sed -i 's/^borg:!:/borg:*:/' /etc/shadow \
     && mkdir -p /srv/borg /etc/ssh/hostkeys /etc/ssh/authorized_keys \
     && chown borg:borg /srv/borg
    
    COPY sshd_config /etc/ssh/sshd_config
    COPY authorized_keys /etc/ssh/authorized_keys/borg
    RUN chmod 644 /etc/ssh/authorized_keys/borg
    
    COPY entrypoint.sh /entrypoint.sh
    RUN chmod +x /entrypoint.sh
    
    EXPOSE 22
    ENTRYPOINT ["/entrypoint.sh"]

    Fichero replica/entrypoint.sh

    #!/bin/sh
    set -e
    if [ ! -f /etc/ssh/hostkeys/ssh_host_ed25519_key ]; then
      ssh-keygen -t ed25519 -N "" -f /etc/ssh/hostkeys/ssh_host_ed25519_key
    fi
    chmod 600 /etc/ssh/hostkeys/ssh_host_ed25519_key
    chown -R borg:borg /srv/borg
    exec /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
  3. Configura borgmatic, crea los repositorios y activa append-only

    Ahora el fichero principal. La passphrase del repositorio entra por la variable de entorno BORG_PASSPHRASE, que Borg lee tal y como describe su guía rápida; defínela en un fichero .env junto al Compose y reemplaza CHANGE_ME_REPO_PASSPHRASE por una frase larga y única. Guarda además esa passphrase en tu gestor de contraseñas: sin ella, las copias son irrecuperables.

    Las rutas de datos se montan en solo lectura. La carpeta de claves SSH se monta también en solo lectura, y el fichero de hosts conocidos se redirige a la caché de Borg (un volumen con escritura) con UserKnownHostsFile. Fijar stop_grace_period da tiempo a que una copia en curso termine limpiamente al parar el contenedor, como recomienda el README de la imagen.

    En la configuración usamos archive_name_format con un prefijo fijo: el README de la imagen advierte de que el hostname del contenedor cambia al reconstruirlo y eso rompe la poda. Cada repositorio declara su modo de cifrado con la clave encryption; es la única fuente del modo, así que repo-create se ejecuta sin la opción --encryption. Los dos repositorios se copian en secuencia.

    Arrancamos con el comando forzado sin --append-only, esperamos unos segundos a que sshd acepte conexiones y creamos ambos repositorios. Si el repo-create falla por conexión rechazada, espera unos segundos más y repítelo (ya existentes se omiten). Cuando los repositorios existen, cambiamos replica/authorized_keys para añadir --append-only, reconstruimos y recreamos solo la réplica; los volúmenes conservan el repositorio remoto. La primera conexión aceptará la huella de host con StrictHostKeyChecking=accept-new; en una réplica real puedes preferir verificarla manualmente.

    La retención (keep_daily, keep_weekly, keep_monthly) y las comprobaciones (checks con frequency) son opciones de configuración de borgmatic. Si tu versión rechaza alguna, genera el fichero de referencia con borgmatic config generate y compáralo.

    Fichero .env

    BORG_PASSPHRASE=CHANGE_ME_REPO_PASSPHRASE
    TZ=Europe/Madrid

    Sustituye cada valor CHANGE_ME_… por uno tuyo antes de arrancar: no dejes contraseñas ni claves de ejemplo.

    Fichero borgmatic/config.yaml

    source_directories:
      - /mnt/source
    
    repositories:
      - path: /mnt/borg-repository
        label: local
        encryption: repokey-blake2
      - path: ssh://borg@replica:22/srv/borg/lab.borg
        label: replica
        encryption: repokey-blake2
    
    ssh_command: ssh -i /root/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.cache/borg/known_hosts -o ServerAliveInterval=10 -o ServerAliveCountMax=30
    
    archive_name_format: 'servidor-casa-{now:%Y-%m-%d-%H%M%S}'
    
    keep_daily: 7
    keep_weekly: 4
    keep_monthly: 6
    
    checks:
      - name: repository
        frequency: 1 week
      - name: archives
        frequency: 2 weeks

    Fichero compose.yaml

    services:
      borgmatic:
        image: ghcr.io/borgmatic-collective/borgmatic:2.1.10
        container_name: borgmatic
        hostname: borgmatic
        restart: unless-stopped
        stop_grace_period: 10m
        depends_on:
          - replica
        environment:
          - TZ=${TZ}
          - BORG_PASSPHRASE=${BORG_PASSPHRASE}
          - CRON=0 2 * * *
        volumes:
          - ./datos:/mnt/source:ro
          - ./repositorio:/mnt/borg-repository
          - ./borgmatic:/etc/borgmatic.d
          - ./ssh-cliente:/root/.ssh:ro
          - borg-config:/root/.config/borg
          - borg-cache:/root/.cache/borg
          - borgmatic-state:/root/.local/state/borgmatic
    
      replica:
        build: ./replica
        container_name: borg-replica
        restart: unless-stopped
        ports:
          - "127.0.0.1:2222:22"
        volumes:
          - replica-repo:/srv/borg
          - replica-hostkeys:/etc/ssh/hostkeys
    
    volumes:
      borg-config:
      borg-cache:
      borgmatic-state:
      replica-repo:
      replica-hostkeys:
    docker compose build replica
    docker compose up -d
    sleep 10
    docker compose exec borgmatic borgmatic repo-create
    docker compose logs --tail 20 replica

    Fichero replica/authorized_keys

    command="borg serve --append-only --restrict-to-path /srv/borg",restrict CHANGE_ME_PUBLIC_KEY

    Sustituye cada valor CHANGE_ME_… por uno tuyo antes de arrancar: no dejes contraseñas ni claves de ejemplo.

    docker compose build replica
    docker compose up -d --force-recreate replica
  4. Haz la primera copia, comprueba y restaura

    Lanzamos una copia manual con --verbosity 1 --list --stats para ver qué archivos entran y cuánto ocupa lo nuevo. Luego listamos los archivos con repo-list, ejecutamos las comprobaciones con check y, lo más importante, restauramos con extract a una carpeta temporal. Una copia que nunca se ha restaurado es solo una esperanza.

    Para restaurar usamos --destination y --archive latest, de modo que no tocamos tus datos reales. Borg guarda las rutas sin la barra inicial, así que el archivo /mnt/source/prueba.txt queda en mnt/source/prueba.txt dentro del destino; el find lo muestra antes de leerlo.

    docker compose exec borgmatic borgmatic create --verbosity 1 --list --stats
    docker compose exec borgmatic borgmatic repo-list

    Salida esperada (ilustrativa: el formato exacto puede variar)

    servidor-casa-2025-01-01-020000  Wed, 2025-01-01 02:00:00 [...]
    docker compose exec borgmatic borgmatic check --verbosity 1
    docker compose exec borgmatic mkdir -p /tmp/restauracion
    docker compose exec borgmatic borgmatic extract --archive latest --repository local --destination /tmp/restauracion
    docker compose exec borgmatic find /tmp/restauracion -name prueba.txt
    docker compose exec borgmatic cat /tmp/restauracion/mnt/source/prueba.txt

Comprueba que funciona

  1. Ambos repositorios contienen la copia

    repo-list muestra los archivos de los repositorios declarados; con dos repositorios, la salida los separa por etiqueta. Los nombres y fechas serán distintos a los de la salida de ejemplo. Si falta el repositorio replica, la copia a la réplica ha fallado: revisa el apartado de problemas frecuentes.

    docker compose exec borgmatic borgmatic repo-list
  2. La restauración devuelve el contenido original

    El cat del fichero restaurado en /tmp/restauracion debe mostrar el mismo texto que escribiste en datos/prueba.txt. Esa es la prueba real de que la copia sirve, no solo de que existe.

    cat datos/prueba.txt
    docker compose exec borgmatic cat /tmp/restauracion/mnt/source/prueba.txt
  3. El cron está programado

    La variable CRON programa la ejecución diaria a las 02:00. Según el README de la imagen, el comando por defecto es borgmatic-start --stats -v 0, que imprime poco. Para confirmar que la tarea está instalada, lista la crontab dentro del contenedor: debe aparecer la línea con 0 2 * * *.

    docker compose exec borgmatic crontab -l
Si algo falla

Permission denied (publickey) al copiar a la réplica

Comprueba que pegaste la línea completa de id_ed25519.pub en replica/authorized_keys (sustituyendo CHANGE_ME_PUBLIC_KEY) y que reconstruiste la imagen con docker compose build replica. Verifica también que ssh-cliente/id_ed25519 pertenece a root y tiene permisos 600.

docker compose build replica
docker compose up -d --force-recreate replica

Failed to create/acquire the lock en el repositorio remoto

Suele ocurrir tras una conexión cortada: el proceso borg serve del servidor sigue vivo sosteniendo el bloqueo. La documentación de borg serve recomienda los keepalives de SSH (ya incluidos en este laboratorio, tanto en ssh_command como en sshd_config) y esperar a que expiren; reiniciar la réplica también libera el proceso.

docker compose restart replica

La poda no encuentra archivos o los borra todos tras reconstruir el contenedor

El nombre de host del contenedor cambia al reconstruirlo y, con el formato de nombre por defecto, borgmatic ya no reconoce los archivos anteriores. Por eso la configuración fija archive_name_format con un prefijo constante; no lo quites.

Passphrase incorrecta o el repositorio no se desbloquea

Verifica que el .env está junto al compose.yaml y que BORG_PASSPHRASE coincide con la usada al crear el repositorio. Tras cambiarla, recrea el contenedor para que la tome.

docker compose up -d --force-recreate borgmatic
Endurece la instalación

Guarda la passphrase y la clave fuera del servidor

Anota la passphrase del repositorio en un gestor de contraseñas y exporta la clave del repositorio a un lugar separado. Si pierdes ambas, los datos cifrados no se pueden recuperar.

Pon una cuota al repositorio remoto

borg serve admite --storage-quota, que fija una cuota de almacenamiento para el repositorio y tiene prioridad sobre la cuota propia del repositorio. Añádela al comando forzado de authorized_keys con un valor por debajo del espacio libre real de tu disco (aquí 10G, dentro de los 20 GB del laboratorio). Después reconstruye y recrea la réplica con los mismos comandos del paso 3 (docker compose build replica y docker compose up -d --force-recreate replica).

Fichero replica/authorized_keys

command="borg serve --append-only --restrict-to-path /srv/borg --storage-quota 10G",restrict CHANGE_ME_PUBLIC_KEY

Sustituye cada valor CHANGE_ME_… por uno tuyo antes de arrancar: no dejes contraseñas ni claves de ejemplo.

Mantén la réplica fuera del alcance del cliente

Append-only no impide prune ni delete según la documentación de Borg, así que lo ideal es que la réplica viva en otra máquina bajo otra cuenta y que la poda del remoto la ejecutes tú desde allí, no desde el servidor que haces copiar.

Mantén el SSH de la réplica en local

El Compose publica el puerto solo en 127.0.0.1. Si trasladas la réplica a otro equipo, expón SSH únicamente a las direcciones que lo necesiten y conserva la autenticación solo por clave.

Limpieza: deshacer el laboratorio

Elimina el laboratorio por completo

down -v borra los contenedores y los volúmenes nombrados, incluida la réplica remota y sus claves de host. --rmi local elimina la imagen construida. Después borra la carpeta del laboratorio, que contiene la copia local cifrada y la clave SSH de prueba (la clave privada pertenece a root, por eso usamos sudo).

docker compose down -v --rmi local
cd ..
sudo rm -rf borg-lab

Qué está verificado

  • replica/sshd_config: sin validador automático, revísalo a mano
  • replica/authorized_keys: sin validador automático, revísalo a mano
  • replica/Dockerfile: sin validador automático, revísalo a mano
  • .env: sin validador automático, revísalo a mano
  • Sintaxis de borgmatic/config.yaml (parser YAML)
  • Sintaxis de compose.yaml (parser YAML y estructura de Compose)
  • replica/authorized_keys: sin validador automático, revísalo a mano
  • replica/authorized_keys: sin validador automático, revísalo a mano
  • Sintaxis de 13 bloque(s) de comandos (bash -n en un contenedor aislado, sin ejecutarlos)
  • Versiones (4), opciones (9) y claves de configuración (27) contrastadas con 18 páginas de documentación oficial
  • Arranque real del laboratorio: no ejecutado

Comprobaciones automáticas del 9 de noviembre de 2026. Lo marcado con «·» no se ha ejecutado: revísalo en tu entorno.

Fuentes

Artículo generado mediante AI.larebelion

Kernel

· Dirección de tecnología · España

«Una copia sin restauración probada es solo una esperanza con buen cifrado.»