Scheduled rsync+ssh push of local dirs to a remote — Go scheduler on Alpine, interval or exec-run
10K+
Push local directories to a remote host over rsync-and-ssh on a schedule. Structured logs, no metrics, no open ports.
Reads a YAML config defining N sync jobs. For each job it runs rsync over ssh to push a local directory one-way to a remote host. Every pass executes inside the long-lived daemon regardless of how it was triggered, so its structured logs (logfmt, UTC timestamps) always land on the container's log stream, ready for a log aggregator (Alloy, Promtail) and alerting.
[user@]host:/path--delete, --chown=uid:gid, and exclude patterns--delete pass. A local path that does not exist fails the job. The guard is a preflight snapshot, and rsync rebuilds its file list after the check. It cannot protect a source that becomes empty during a pass; use max_delete as the backstop, as the example shows. The guard checks only the built-in global excludes, not per-job excludes. For a delete: true job whose own excludes can match every entry, set max_delete to cap the deletions. The cap fails only when more than N files would be deleted, so a value at or above the mirror's file count never fires.sync subcommanddocker pull cplieger/docker-rsync-scheduler:latest
Also published to ghcr.io/cplieger/docker-rsync-scheduler with identical images and tags. Release versions are tagged vX.Y.Z alongside latest.
# Example compose for docker-rsync-scheduler. See the README for all configuration options and hardening.
services:
rsync:
image: ghcr.io/cplieger/docker-rsync-scheduler:latest
container_name: rsync
restart: unless-stopped
environment:
SYNC_INTERVAL: "6h" # built-in cadence (Go duration); "off" = external trigger; see README "Scheduling modes"
SYNC_TIMEOUT: "10m" # per-job rsync timeout (Go duration)
volumes:
- "./config.yaml:/config/config.yaml:ro"
- "./id_ed25519:/keys/id_ed25519:ro" # dedicated SSH key, host file mode 0600
- "./data:/data" # last-run record; keeps the schedule across recreates and image updates (see README "Scheduling modes")
# source directories referenced by the jobs in config.yaml:
- "/srv/source/certs:/sources/certs:ro"
- "/srv/source/appconfig:/sources/appconfig:ro"
Apache-2.0. See LICENSE. The image carries the license text of every bundled component under /usr/share/licenses/. The Alpine packages in the image ship no license file upstream, so their license texts are kept under licenses/ in this repository and copied in.
The bundled component is rsync itself, which is GPL-3.0-or-later. The build fetches the pinned release tarball https://download.samba.org/pub/rsync/src/rsync-3.5.0.tar.gz (RSYNC_VERSION=v3.5.0), verifies the detached upstream signature and then the pinned SHA256, and applies no patches to the extracted source. rsync's own COPYING travels in the image at /usr/share/licenses/rsync/COPYING, and the upstream project is RsyncProject/rsync. That tarball and this repository's Dockerfile are the complete recipe for the rsync binary in the image, which is how anyone who receives it gets the corresponding source.
Content type
Image
Digest
sha256:953abaed7…
Size
8.3 MB
Last updated
about 13 hours ago
docker pull cplieger/docker-rsync-scheduler