Container images with rsync, which copies and synchronizes files locally, over SSH or through an rsync daemon, and sends only the parts of files that changed. rsync is compiled from the signed release tarball on Ubuntu and Alpine, for linux/amd64 and linux/arm64, and the images are rebuilt when rsync publishes a release and when the base image changes.
This is an unofficial build, not affiliated with or endorsed by the rsync project. Report problems with the image in this repository and rsync bugs upstream.
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/randomcontainers/rsync \
-a --delete photos/ photos-backup/Show what the same command would change, without copying anything:
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/randomcontainers/rsync \
-a --delete --dry-run --itemize-changes photos/ photos-backup/The entrypoint runs rsync under tini in /work, so relative paths start in the directory you mount. To copy between two directories on the host, mount both:
docker run --rm --user "$(id -u):$(id -g)" -v /srv/data:/src:ro -v /mnt/backup:/dst \
ghcr.io/randomcontainers/rsync -a --delete /src/ /dst/data/The rsync manual covers the options and filter rules, and rsyncd.conf the daemon configuration.
rsyncin/usr/local/bin, with ACLs (-A), extended attributes (-X), zstd and LZ4 compression next to zlib, xxHash checksums, OpenSSL digests (SHA-1 checksums, SHA-256 and SHA-512 for daemon logins) and internationalized host names.sshfrom the OpenSSH client, which rsync uses as its remote shell.- The distro's libacl, popt, OpenSSL, zstd, LZ4, xxHash and libidn2. rsync's own copy of zlib is compiled in.
Not included: rsync-ssl, a bash script that needs openssl, stunnel or gnutls-cli; rrsync, which needs Python; and the man pages. The configure flags and the output of rsync --version are in /usr/local/share/randomcontainers/rsync/buildinfo.
rsync's default image adds no other tools, so latest and slim are the same image, with the contents listed above. Use latest to run it and the slim tags as a base for your own image.
<version> is an rsync release such as 3.5.1. <minor> and <major> are its shorter forms, 3.5 and 3, and follow the newest release in that series. Each row lists the default tag and its slim twin, which point to the same image.
| Tags | Base |
|---|---|
latest, slim |
Ubuntu |
<version>, <version>-slim |
Ubuntu |
<minor>, <minor>-slim, <major>, <major>-slim |
Ubuntu |
ubuntu, slim-ubuntu |
Ubuntu |
<version>-ubuntu, <version>-slim-ubuntu |
Ubuntu |
<minor>-ubuntu, <minor>-slim-ubuntu, <major>-ubuntu, <major>-slim-ubuntu |
Ubuntu |
<version>-ubuntu26.04, <version>-slim-ubuntu26.04 |
Ubuntu 26.04 |
alpine, slim-alpine |
Alpine |
<version>-alpine, <version>-slim-alpine |
Alpine |
<minor>-alpine, <minor>-slim-alpine, <major>-alpine, <major>-slim-alpine |
Alpine |
<version>-alpine3.24, <version>-slim-alpine3.24 |
Alpine 3.24 |
The images are currently built on Ubuntu 26.04 and Alpine 3.24. Tags without a distro version move to the next distro release when the project does; tags ending in ubuntu26.04 or alpine3.24 stay on that release and are no longer rebuilt once the project moves to the next one. Every tag of the current rsync version, including the exact version, is rebuilt in place (see Updates), so pin a digest when you need the same bytes every time.
linux/amd64 and linux/arm64, for both Ubuntu and Alpine. Both are compiled natively on GitHub-hosted runners, without emulation.
The working directory is /work. The image runs as UID 1000, and any other UID works too: HOME is then /, and caches go to /cache, which anyone can write to. How to get output files owned by you depends on how you run containers:
| Runtime | Flag |
|---|---|
| Docker on Linux (rootful), GitHub Actions | --user "$(id -u):$(id -g)" |
| Rootless Podman | --userns=keep-id |
| Rootless Docker | --user 0:0 (root in the container is your user on the host) |
| Docker Desktop on macOS or Windows | none, file ownership is mapped for you |
-a includes -o and -g, but only root can hand files to another owner. As any other user, rsync keeps modes and times, and the copies belong to the UID and GID the container runs as. To keep owners and groups, run the container with --user 0:0, or pass --fake-super to store them in extended attributes instead.
rsync starts ssh from the image as its remote shell. ssh exits with No user exists for uid when the UID has no entry in /etc/passwd, and the only regular user in the image is UID 1000 (ubuntu on Ubuntu, app on Alpine). Run SSH transfers as UID 1000 and pass the key and known_hosts file explicitly:
docker run --rm --user 1000:1000 -v "$PWD:/work" -v "$HOME/.ssh:/ssh:ro" \
ghcr.io/randomcontainers/rsync -a \
-e "ssh -i /ssh/id_ed25519 -o UserKnownHostsFile=/ssh/known_hosts" \
site/ deploy@example.com:/var/www/site/UID 1000 has to be able to read the key, and the server has to be in known_hosts already: without a terminal, ssh cannot ask whether to trust a new host key and refuses to connect. Files that rsync writes to /work belong to UID 1000. For another UID, add a user for it in your own image (see Extending the slim image).
A config file for one writable module, with use chroot = no because only root can chroot:
use chroot = no
log file = /work/rsyncd.log
[backup]
path = /work/backup
read only = no
auth users = backup
secrets file = /work/rsyncd.secrets
auth digest = sha512rsyncd.secrets holds a backup:<password> line and must not be readable by other users (chmod 600). auth digest = sha512 refuses clients that can only log in with MD4 or MD5: rsync before 3.2.7, rsync built without OpenSSL (Alpine's rsync package, for one) and the openrsync that ships with macOS. The daemon does not create the module directory, so create backup/ next to the config file first. Then start the daemon in the foreground on a port above 1024 and publish that port:
docker run -d --name rsyncd --user "$(id -u):$(id -g)" -p 873:8730 -v "$PWD:/work" \
ghcr.io/randomcontainers/rsync --daemon --no-detach --port=8730 --config=/work/rsyncd.confClients connect with rsync -a files/ rsync://backup@<host>/backup/ and give the password in RSYNC_PASSWORD or with --password-file. The daemon protocol is not encrypted, so run it only on a network you trust. rsync releases often fix security issues in path handling and the daemon, and the images pick up a new release about a day after it is published.
Use a slim tag as the base for your own image. slim, slim-ubuntu and slim-alpine move to each new rsync release and are rebuilt when the base image changes. Switch to root to change the image, then back. This adds a user for UID 1001, so that ssh can run as that UID:
FROM ghcr.io/randomcontainers/rsync:slim-ubuntu@sha256:...
USER root
RUN groupadd --gid 1001 deploy \
&& useradd --uid 1001 --gid 1001 --create-home deploy
USER 1001:1001On Alpine, start from slim-alpine and use addgroup -g 1001 deploy && adduser -D -u 1001 -G deploy deploy. The entrypoint is ["tini", "--", "rsync"]; set your own ENTRYPOINT if your image runs something else. To pick up new rsync releases and base image fixes, let Dependabot or Renovate update the digest in your FROM line.
Everything the image adds is under /usr/local. /usr/local/share/randomcontainers/rsync/ holds the version, the source URLs, the build options, the license files and runtime-deps, the list of distro packages rsync needs at run time.
Each image has a build provenance attestation from this repository's GitHub Actions run, signed by the shared build workflow in randomcontainers/ci:
gh attestation verify oci://ghcr.io/randomcontainers/rsync:latest \
--repo randomcontainers/rsync --signer-repo randomcontainers/ciEach platform image also carries an SPDX SBOM that lists every distro package with its version:
docker buildx imagetools inspect ghcr.io/randomcontainers/rsync:latest --format '{{ json .SBOM }}'Before compiling, the build checks the tarball against the SHA-256 recorded in package.yml and its signature against the rsync release signing keys in keys/rsync-release.gpg: Andrew Tridgell's (fingerprint 9FEF 112D CE19 A0DC 7E88 2CB8 1BB2 4997 A853 5F6F) and Zen Dodd's (fingerprint C0E1 0545 0570 4F75 7D92 5AB2 6A64 E8AA 2EFB CF5D), who signed 3.5.1.
The project checks the releases of RsyncProject/rsync every 15 minutes and skips pre-releases. A release is picked up once it is 24 hours old. Its tarball is checked against the SHA-256 digest GitHub recorded for the release asset, the new version and the tarball's SHA-256 are committed to package.yml, and the images are rebuilt. A release signed with a key that is not in keys/rsync-release.gpg fails to build until the key is added. Only the newest release is built; tags of older versions stay as they were last built.
The images of the current version are also rebuilt when the Ubuntu or Alpine base image changes and at least every 7 days, so distro security fixes reach the current tags.
docker build -f Dockerfile.ubuntu --target slim \
--build-arg VERSION=<version> \
--build-arg SOURCE_SHA256=<sha256 from package.yml> \
-t rsync:local .Use Dockerfile.alpine for the Alpine image. --build-arg JOBS=<n> limits the number of parallel compile jobs.
rsync is licensed under the GNU General Public License, version 3 or later (GPL-3.0-or-later), with an added permission to link it with the OpenSSL and xxHash libraries. The build compiles in rsync's copy of zlib, which is under the zlib license, so the image's license label is GPL-3.0-or-later AND Zlib. rsync's COPYING and the zlib license (COPYING.zlib) are in /usr/local/share/randomcontainers/rsync/licenses/. The OpenSSH client and the other Ubuntu and Alpine packages in the image keep their own licenses.
The corresponding source for each image:
- rsync: every version has a GitHub release in this repository, named
v<version>, with the exactrsync-<version>.tar.gzthat was compiled and its signature. The build applies no patches./usr/local/share/randomcontainers/rsync/sourcelists that release and the upstream download URLs. - Build scripts: this repository at the commit in the image's
org.opencontainers.image.revisionlabel. The Dockerfiles hold every configure flag. - Ubuntu packages: the source packages on Launchpad for the versions listed in the SBOM.
apt-get source <package>=<version>fetches a version that is still in the Ubuntu archive. - Alpine packages: Alpine has no source packages. For the versions listed in the SBOM, the source is the APKBUILD and patches in aports, branch
3.24-stable, and the archives on distfiles.alpinelinux.org.
The files in this repository are available under the MIT license, see LICENSE.
To suggest another tool, use the Request a tool form.