# Verified compose for Syncthing, tested 2026-10-16 by the SelfHostBench lab (3 runs; the figures are the median). # Lab host: AMD EPYC 7763 64-Core Processor, 2 vCPU, 7.8 GiB RAM, x86_64, Docker 28.0.4. # Floating image tags below were replaced by the version resolved at test time; # the registry digest is in the comment on each image line. # Measurements: syncthing.json (schema 3). # Lab difference: Syncthing is peer to peer, so the lab runs TWO nodes: the published service (node A) and a second, identical service (node B, `syncthing-b`), which is not in the published file. The lab publishes no host ports, puts both on a Compose network with no route out (`internal: true`), gives each node a fixed GUI API key, and mounts one shared volume at /data in both nodes so the load step can write into node A's folder and read the files node B received. A real deployment has one node per device, on different machines. Image, user, volume at /var/syncthing and healthcheck are the published file's. The published single-node file has not been run with its ports mapped in the lab. # Syncthing 2.1.6: continuous peer-to-peer file synchronisation. This is ONE node, the thing you run on a server and pair your laptop, phone and other machines with. # Syncthing has no central server and no upload API: every device runs the same program, and a folder is shared by adding the other device's ID on both sides. # # Follows README-Docker.md of the Syncthing repository (tag v2.1.6). Differences from it, all marked below: a pinned tag, a named volume instead of ./st-sync, # published ports on a bridge network instead of `network_mode: host`, the web interface bound to localhost only, a 5-second healthcheck. # The GUI has no password after the first start. Set one (Actions > Settings > GUI, "GUI Authentication User" and "Password") before you publish the port to anything but localhost. name: syncthing services: syncthing: image: syncthing/syncthing:2.1.6 # sha256:4d0281bdcb4ed9baf503d251d0544ddd856f49fce3f114056257ae172b7fb670 (version v2.1.6, from label:org.opencontainers.image.version) # The name other devices show for this one. Change it. hostname: my-syncthing restart: unless-stopped environment: # The user the process runs as (the image starts as root and switches). Files in your folders are owned by this user. PUID: "1000" PGID: "1000" # The image sets STGUIADDRESS=0.0.0.0:8384 so the GUI answers on the Docker network; the port mapping below keeps it on localhost of the host. ports: # Web GUI and REST API. Localhost only: reach it with an SSH tunnel (ssh -L 8384:127.0.0.1:8384 you@server) or put a TLS reverse proxy in front. - "127.0.0.1:8384:8384" # Sync protocol (TCP, and QUIC over UDP). Other devices connect here; open it in the server's firewall. - "22000:22000/tcp" - "22000:22000/udp" # Local discovery (UDP broadcast). It does not cross Docker's bridge network, so on a LAN it finds nothing from here; README-Docker.md recommends # `network_mode: host` for that reason (then delete this `ports:` block). On a VPS, devices find each other by address or global discovery, and this line is unneeded. - "21027:21027/udp" volumes: # Configuration, keys, the index database and the default folder (~/Sync = /var/syncthing/Sync). Your device ID comes from the key in here: back it up. - syncthing:/var/syncthing # More folders: mount them below and add them in the GUI. A named volume or a path, for example: # - /srv/files:/data/files healthcheck: # The image's own check (the no-auth health endpoint) with a 5 s interval instead of 60 s, so the container is "healthy" a few seconds after the start. test: ["CMD-SHELL", "curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1"] interval: 5s timeout: 5s start_period: 5s retries: 3 volumes: syncthing: