Skip to content

You're reading the docs for v1.0, an older version. Read the latest docs

GuidesContainer

Container

Run the GHCR image with a mounted config, keep a log on the host, pin a version, and schedule it.

The image is ghcr.io/netspeedy/s3mirror, built for linux/amd64 and linux/arm64 from the repository’s Dockerfile: a slim Python image with the three packages installed and s3mirror.py copied in. Its entrypoint is s3mirror, so everything after the image name is passed to s3mirror as flags. With no flags it prints its help.

Mount the config file read-only and point --config at it:

docker run --rm \
-v "$PWD/config.yaml:/config/config.yaml:ro" \
ghcr.io/netspeedy/s3mirror:v1.0.4 \
--config /config/config.yaml --no-delete --debug

Output goes to the container’s standard output unbuffered, so docker logs and CI logs show it as it happens, and docker run exits with s3mirror’s exit code.

The container needs to reach both endpoints. If an endpoint’s hostname only resolves on the host, or it’s on localhost, run with --network host or use an address the container can reach.

Every stable release is published under four tags:

TagExampleMoves when
The release tagv1.0.4Never
The version1.0.4Never
The minor line1.0Each patch release in that line
latestlatestEach release

Pin the release tag or the version for scheduled jobs, so an upgrade happens when you choose. Images are published only for stable tags on main, after a check that the tag matches the script’s __version__, and carry build provenance and an SBOM.

Mount a directory for the log and pass --log-file inside it:

docker run --rm \
--user "$(id -u):$(id -g)" \
-v /etc/s3mirror.yaml:/config/config.yaml:ro \
-v /var/log/s3mirror:/logs \
ghcr.io/netspeedy/s3mirror:v1.0.4 \
--config /config/config.yaml --log-file /logs/s3mirror.log

The image runs as an unprivileged s3mirror user created at build time, with no fixed UID, so it usually can’t write to a host directory owned by someone else. --user runs it as your own UID and GID instead, which can write wherever you can; the script and its packages are readable by any user. If the log can’t be opened, s3mirror stops with a traceback and exit code 1 before connecting to anything.

The config only has to be readable by the user the container runs as. With --user, a config that’s chmod 600 and owned by you works.

If an endpoint uses a certificate from your own CA, mount the CA bundle and give its path inside the container as verify_ssl:

docker run --rm \
-v /etc/s3mirror.yaml:/config/config.yaml:ro \
-v /etc/ssl/certs/internal-ca.pem:/config/internal-ca.pem:ro \
ghcr.io/netspeedy/s3mirror:v1.0.4 \
--config /config/config.yaml
/etc/s3mirror.yaml
destination:
endpoint_url: "https://minio.internal.example:9000"
aws_access_key_id: "DEST_ACCESS_KEY"
aws_secret_access_key: "DEST_SECRET_KEY"
verify_ssl: "/config/internal-ca.pem"

Start the container from the host’s scheduler with --rm, the same way Scheduling starts the installed command. In a systemd unit:

/etc/systemd/system/s3mirror.service
[Unit]
Description=Mirror S3 buckets with s3mirror
Wants=network-online.target
After=network-online.target docker.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/docker run --rm --init --name s3mirror \
--user 1000:1000 \
-v /etc/s3mirror.yaml:/config/config.yaml:ro \
-v /var/log/s3mirror:/logs \
ghcr.io/netspeedy/s3mirror:v1.0.4 \
--config /config/config.yaml --log-file /logs/s3mirror.log

Pair it with the timer on the Scheduling page. Use the UID and GID that own /var/log/s3mirror in --user. The fixed --name makes a second container refuse to start while one is still running.

s3mirror runs as process 1 in the container and doesn’t handle SIGTERM, and Linux ignores a signal that process 1 has no handler for. So docker stop waits out its timeout, 10 seconds by default, and then kills the container, which exits 137. With --init, as in the unit above, a small init process passes SIGTERM on and s3mirror stops at once. Either way it stops mid-run without a summary; the next run picks up where it left off.

Ctrl+C on an attached docker run is passed on as SIGINT, which s3mirror does handle: it waits for the copies or deletions already queued for the current bucket, then exits 130 without a summary.