Efficient React App Deployment Using rsync: A Practical Guide
When it comes to deploying a React application, many developers reach for tools like FTP, scp
, or Docker. While those methods have their place, if you need a fast, reliable, and incremental way to sync your build directory (usually dist/
or build/
), there’s arguably no better tool than rsync
.
This article dives into why rsync
is ideal for syncing React builds, how to use it effectively, and the gotchas and best practices you’ll want to be aware of — especially when integrating into a CI/CD pipeline like Drone CI.
⚡ Why Use rsync
?
Here are a few reasons rsync
stands out for syncing front-end build files:
- Incremental transfer: Only modified files are sent over the wire.
- Built-in deletion logic: Old files can be removed automatically.
- SSH support: Secure transmission using private keys.
- Cross-platform: Works on Linux, macOS, and even Windows via WSL or Git Bash.
- Script-friendly: Perfect for use in CI/CD pipelines.
📁 The Problem With Hashed Files
Most React/Vite/Webpack builds generate content-hashed filenames like:
app.18e21a0d.js
vendor.8734dsf8.js
style.abcd123.css
Every build produces new filenames, and the old ones are obsolete. If you deploy without cleaning the destination directory, your users might receive stale or conflicting assets, especially when caching is involved.
This is where --delete
becomes critical in your rsync
command.
✅ The Recommended rsync
Command
rsync -avzh --delete --info=progress2 ./dist/ user@your-server:/var/www/your-app/
Let’s break this down:
Flag | Description |
---|---|
-a |
Archive mode: preserves file permissions, timestamps, symbolic links, etc. |
-v |
Verbose: prints what’s being transferred. Useful for logs. |
-z |
Compress file data during transfer — helps speed on slower networks. |
-h |
Human-readable output for file sizes (e.g., 4.2M instead of 4378923 ). |
--delete |
Deletes files on the target that are no longer present in source (essential for hashed builds). |
--info=progress2 |
Displays total progress instead of per-file progress (cleaner for large builds). |
./dist/ |
The trailing slash ensures only the contents of dist are synced, not the folder itself. |
user@your-server:/.../ |
SSH target and destination directory. |
🧪 Optional: Per-file Progress
If you prefer to see progress for each individual file, replace --info=progress2
with --progress
:
rsync -avzh --delete --progress ./dist/ user@host:/path/
This is more verbose but useful during debugging or manual deployment.
🔐 SSH Key Considerations
When using rsync
over SSH (which is most common), you’ll want to ensure:
- Passwordless login is set up using a private key.
- The remote user (e.g.,
www-data
,deployer
,ubuntu
, etc.) has write permissions to the target directory.
In CI, use:
-e "ssh -i /path/to/key -o StrictHostKeyChecking=no"
You can even mount the key from a Drone secret or environment variable and assign the correct permissions with chmod 600
.
🤖 Integrating with Drone CI
Here's a minimal Drone CI pipeline step using rsync
:
steps:
- name: deploy-to-staging
image: alpine
commands:
- apk add --no-cache openssh rsync
- chmod 600 /home/drone/staging_key
- echo "Syncing dist to staging server..."
- rsync -avzh --delete --info=progress2 \
-e "ssh -i /home/drone/staging_key -o StrictHostKeyChecking=no" \
/var/www/html/my-app/dist/ \
user@staging-server:/var/www/html/your-app/
Be sure to:
- Add
staging_key
as a secret. - Use the correct user and path.
- Ensure the
dist/
folder is already built before this step runs.
🚨 Things to Watch Out For
Issue | Recommendation |
---|---|
Stale Files Remain | Always use --delete if your build uses hashed filenames. |
Wrong folder structure | Use a trailing / on the source path to sync contents, not the directory itself. |
Permission denied | Double-check remote write permissions for the SSH user. |
Missing tools | Install rsync and ssh inside CI containers if not present (e.g., Alpine uses apk add ). |
CI log spam | Use --info=progress2 instead of --progress to reduce noise. |
🔁 Alternative: scp
vs rsync
Tool | Behavior |
---|---|
scp |
Always copies everything — no diffing, no deletion, slower over time. |
rsync |
Smart diffing, can delete, and more efficient. Better for frequent and incremental deploys. |
🏁 Finally
Using rsync
for syncing your React app’s dist/
folder is a clean, fast, and robust method. It works great locally and scales well to CI/CD environments, especially when you want deterministic builds and clean deployments without stale artifacts.
If you’re deploying hashed frontend builds and care about efficiency and cache hygiene, rsync --delete
is not optional — it’s essential.
Comments ()