If you just need to restore older versions of a (non-text) doc and don't need to support things like diff and merge, a handy approach is to store all the steps needed to create the doc, same as you would to implement undo/redo and allow "tagging" the operation stream to create your versions.
There are a lot of ways to optimize this sort of thing in practice so it runs reasonably, but that's an exercise for the implementer.
(oh hey, author of that article here :))
This is something I've been thinking a lot about as well. A cool approach I've been experimenting with is using a CRDT for the underlying document data, and taking snapshots of the logical clock that correspond to version numbers. If you don't garbage-collect the CRDT, you can then restore the value based on the logical clock.
The CRDT itself can be persisted to S3 or similar, as in that article. We’ve been working on this in the open, so you can follow along: https://y-sweet.dev/