I found myself writing a Makefile for several tasks to manage this website, which uses Hugo. Of course, I use rsync, a truly tremendous tool, to deploy the files to my remote server.
This will be a short article about it. Just the basics, really, and probably not even worth an article.
UPDATE: 2026/8/9
I don’t use
rsyncany longer to deploy my website and haven’t for years. Instead, I’ve been using containers. I still usersync, but perhaps for not much longer if the maintainer continues to use AI and cause regressions.Truth be told, even if there weren’t any regressions, I still don’t like the idea of AI being used for anything.
Definition
rsync stands for remote sync and is used to transfer files from a source location to a destination location. The destination can be either local or remote, and it uses ssh as its remote shell by default, although it can use others such as rsh (although, it shouldn’t because it is not secure).
Its algorithm ensures that only the files that have changed since the last sync will be copied over, which it determines by looking at a file’s modification time and its size (this is an over-simplified explanation, of course).
Examples
Here are the flags used in these examples:
-a - Archive mode; equals
-rlptgoD(no-H,-A,-X).-z - Compress file data during the transfer.
-P - Same as
--partial --progress.-v - Increase verbosity.
–delete - Delete files from the destination directory if they are removed from the source.
Let’s create our workspace:
$ mkdir -p tmp/{foo,bar}
$ touch tmp/bar/file_{0..4}
This creates a directory structure like the following:
$ tree tmp
tmp
├── bar
│ ├── file_0
│ ├── file_1
│ ├── file_2
│ ├── file_3
│ ├── file_4
└── foo
3 directories, 5 files
Copying
Now, let’s copy all of the files from bar to foo:
$ rsync -az tmp/bar/ tmp/foo
$ tree tmp
tmp/
├── bar/
│ ├── file_0
│ ├── file_1
│ ├── file_2
│ ├── file_3
│ ├── file_4
└── foo/
├── file_0
├── file_1
├── file_2
├── file_3
├── file_4
3 directories, 10 files
The forward slash (/) after the source location (i.e., tmp/bar/) tells rsync to copy only the contents of bar. If the slash had been omitted, it would have copied the directory, too, which is not what we want.
Observe:
$ rsync -az tmp/bar tmp/foo
$ tree tmp
tmp/
├── bar/
│ ├── file_0
│ ├── file_1
│ ├── file_2
│ ├── file_3
│ └── file_4
└── foo/
└── bar/
├── file_0
├── file_1
├── file_2
├── file_3
└── file_4
4 directories, 10 files
BT no likey.
Verbosity
There’s verbose mode (this is assuming the content of foo/ is empty):
$ rsync -avz tmp/bar/ tmp/foo
sending incremental file list
./
file_0
file_1
file_2
file_3
file_4
sent 315 bytes received 114 bytes 858.00 bytes/sec
total size is 0 speedup is 0.00
And, running again without having modified any of the files won’t transfer anything:
$ rsync -avz tmp/bar/ tmp/foo
sending incremental file list
sent 128 bytes received 12 bytes 280.00 bytes/sec
total size is 0 speedup is 0.00
Now, let’s modify a file by updating its last access and modification time:
$ touch tmp/bar/file_2
rsync will spring into action and copy the updated file metadata to foo/.
$ rsync -avz tmp/bar/ tmp/foo
sending incremental file list
file_2
sent 176 bytes received 35 bytes 422.00 bytes/sec
total size is 0 speedup is 0.00
Weeeeeeeeeeeeeeeeeeeeeeeeeeeee
Syncing
Adding the --delete switch will keep the directories truly in sync by removing any file from the destination location that has already been removed from the source:
$ rm tmp/bar/file_3
tmp/
├── bar/
│ ├── file_0
│ ├── file_1
│ ├── file_2
│ └── file_4
└── foo/
├── file_0
├── file_1
├── file_2
├── file_3
└── file_4
3 directories, 8 files
$ rsync -avz --delete tmp/bar/ tmp/foo
sending incremental file list
deleting file_3
sent 127 bytes received 22 bytes 298.00 bytes/sec
total size is 0 speedup is 0.00
$ tree tmp
tmp/
├── bar/
│ ├── file_0
│ ├── file_1
│ ├── file_2
│ └── file_4
└── foo/
├── file_0
├── file_1
├── file_2
└── file_4
3 directories, 8 files
Remote Transfer
$ rsync -azP --delete bar/ kilgoretrout@vonnegut:/var/www/foo/
Makefile
Here be a simple Makefile:
CC = hugo
FLAGS = -D
DEST = /path/to/document_root/
SYSTEM = kilgoretrout@vonnegut
TARGET = public
.PHONY: build clean deploy serve
build:
$(CC)
clean:
rm -rf $(TARGET)
deploy: $(TARGET)
rsync -azP --delete $(TARGET)/ $(SYSTEM):$(DEST)
serve:
$(CC) $(FLAGS) serve