I do this at the file system level, not the file level, using zfs.
Unless the container has a database, I use zfs snapshots. If it has a database, my script dumps the database first and then does a ZFS snapshot. Then that snapshot is sent via sanoid to a zfs disk that is in a different backup pool.
This is a block level backup, so it only backs up the actual data blocks that changed.
self hosted git repository.
I setup gitea on my server and use it to track version changes of all my scripts.
And I use a combination of the wiki and .md (readme) files for howto’s and any inventory I’m keeping, like IP addresses, CPU assignments etc.
But mainly it’s all in .md formatted with markdown.