All projects

Personal project, Since August 2026

Backhaul

A scheduled backup tool that uploads local folders to Infomaniak kDrive, with configurable retention per folder. It runs in a Docker container.

Details

Role
Design and development
License
Open source, ISC
Distribution
Docker image on GHCR

Context

Local backups, but no off-site copy.

On my home server, the apps already make their own backups: zip files dropped in a folder. But those files stay on the same machine as the data they protect.

I wanted a containerised app that sends them to Infomaniak kDrive, set up with a single config file, without piling them up forever. It was also a chance to see a project through from start to finish: tests, Docker image, published releases.

How it works

  1. Schedule
    A cron expression starts the backup, and optionally the container start.
  2. Selection
    The files of each source folder, except hidden files and recently changed ones.
  3. Upload
    Each file goes to its kDrive folder, unless it is already there with the same hash.
  4. Retention
    Each destination keeps only its most recent files.
  5. Summary
    A summary in the logs, and an exit code that says whether everything succeeded.

One container, two read-only volumes

The container only gets its configuration and the folders to back up, mounted read-only: Backhaul never changes local files.

Each folder is handled on its own. A missing source, such as an unmounted volume, is reported in the logs without blocking the others.

# compose.yml
services:
  backhaul:
    image: ghcr.io/tremblets/backhaul:latest
    restart: unless-stopped
    stop_grace_period: 5m
    environment:
      TZ: Europe/Zurich
    volumes:
      - ./config.yml:/config/config.yml:ro
      - ./data:/data:ro

Choices

  • Never a half-written file

    A file changed too recently waits for the next run. Its age comes from the newer of its mtime and ctime, which also covers copies that keep the original date.
  • The date comes from the file name

    Retention sorts files by the timestamp in their name, not by upload date: a late upload, after an outage, does not distort the order.
  • Stopping deletes nothing

    On shutdown, uploads in progress finish, no new one starts and retention is not applied. The rest goes up on the next run.
  • A strict configuration

    An unknown key, a duplicate destination or an invalid cron expression stop the app from starting, naming the faulty field.

Technologies

Node.js
Cron scheduler, kDrive uploads and a CLI for manual backups.
Docker
Image published on GHCR, run on the home server.
Vitest
Tests for retention, uploads and graceful shutdown.
Zod
Strict validation of config.yml at startup.