Backups and upgrades
The examples use the systemd setup for an
install from source, with data in /var/lib/oneloop/data, and the example
compose.yaml for Docker. Run oneloop commands as the account that runs
oneloop, with the settings from its unit file, for example:
sudo -u oneloop env $(systemctl show oneloop -p Environment --value) /usr/local/bin/oneloop backup create …
This splits the unit’s settings at spaces, so write every value without
spaces, such as ONELOOP_TRUSTED_PROXIES=10.0.0.1,10.0.0.2.
Back up
A backup is a complete, verified copy of the database, all uploaded files and
the keys folder. That folder holds the key that encrypts Knowledge
credentials, so a restored instance can still sync. oneloop keeps running
while you make a backup. While the backup copies the database, file uploads
and downloads wait. One that waits more than 5 seconds fails, and you can try
it again. Image thumbnails aren’t copied; oneloop makes them again when people
view the images.
First, create a backup folder that only the account that runs oneloop can use:
sudo install -d -m 0700 -o oneloop -g oneloop /var/backups/oneloop
Then make a backup in a new folder inside it:
oneloop backup create /var/backups/oneloop/2026-09-28
The destination folder must not exist yet, but its parent folder must. It must be outside the data directory.
A backup that stops halfway, for example because the server restarted, leaves
a hidden folder next to the destination, such as .2026-09-28.partial-…. The
next backup into the same folder removes it, and so does db migrate in its
backup folder. If oneloop can’t tell that the stopped backup has ended, it
prints the folder’s path instead, and you delete the folder; see
Stopped backups.
With Docker, first create a backup folder that the container can write to. The container runs as user 10001:
sudo install -d -m 0700 -o 10001 -g 10001 /srv/oneloop-backups
In compose.yaml, add - /srv/oneloop-backups:/backups to the service’s
volumes:, next to - oneloop-data:/data. Run docker compose up -d, and
then:
docker compose exec oneloop oneloop backup create /backups/2026-09-28
Warning
A backup contains password hashes and every private file. Keep it private, and encrypt copies that you store elsewhere. Don’t copy the data directory by hand while oneloop runs, because the copy can be broken.
Back up every night
Run the command every night with cron or a systemd timer. For example, add this
line to the crontab of the account that runs oneloop, with
sudo crontab -u oneloop -e:
0 2 * * * ONELOOP_DATA_DIR=/var/lib/oneloop/data /usr/local/bin/oneloop backup create /var/backups/oneloop/$(date -u +\%Y\%m\%dT\%H\%M\%SZ)
With Docker, use
docker compose -f /path/to/compose.yaml exec -T oneloop oneloop backup create …
in the host’s crontab. oneloop never deletes old backups, so delete them on
your own schedule.
Test your backups
From time to time, restore a backup into a test folder and look around. Only the account that runs oneloop can read the backup, so use a test folder that this account can write to:
sudo -u oneloop env ONELOOP_DATA_DIR=/var/lib/oneloop/restore-test \
/usr/local/bin/oneloop backup restore /var/backups/oneloop/2026-09-28
sudo -u oneloop env ONELOOP_DATA_DIR=/var/lib/oneloop/restore-test \
ONELOOP_LISTEN=127.0.0.1:19220 ONELOOP_PUBLIC_URL=http://127.0.0.1:19220 \
/usr/local/bin/oneloop serve
Sign in at http://127.0.0.1:19220 and open a few tasks and files. On a remote
server, first open an SSH tunnel with
ssh -L 19220:127.0.0.1:19220 you@your-server. Then stop the test server with
Ctrl+C, and delete the folder with sudo rm -rf /var/lib/oneloop/restore-test.
Restore
oneloop backup restore writes into the configured data directory. This
directory must be empty or not exist, so a restore never overwrites an
instance.
- Stop oneloop.
- Move the current data directory aside. Keep it until you are sure that the
restore worked:
sudo mv /var/lib/oneloop/data /var/lib/oneloop/data.old - Restore the backup:
oneloop backup restore /var/backups/oneloop/2026-09-28 - If the backup comes from an older version, run
oneloop db migrate --backup-dir /var/backups/oneloop. - Start oneloop.
With Docker, run docker compose down, and restore into a new volume:
docker volume create oneloop-restored
docker run --rm -v oneloop-restored:/data -v /srv/oneloop-backups:/backups:ro \
ghcr.io/code19m/oneloop:0.1.0-rc.9 backup restore /backups/2026-09-28
In compose.yaml, change the volume’s name: to oneloop-restored. If the
backup comes from an older version, migrate it as described in
Upgrade. Then run docker compose up -d. Keep the old volume until
you are sure that the restore worked.
After a restore, everyone must sign in again and reconnect their AI assistants, because oneloop cancels all sessions and connections from the backup.
If a restore stops halfway, oneloop refuses to use that directory. Run the same restore again: it first removes what the stopped restore left, and nothing else. If oneloop can’t tell that the stopped restore has ended, for example because it ran on another computer, the restore says so. Then make sure that no restore is running, empty the directory completely, including hidden files, and restore again.
Upgrade
oneloop never changes its database by itself. After you install a new version,
you run oneloop db migrate. It first saves a verified backup in the folder
you choose, and it changes nothing if the backup fails.
- Read the changelog for every version you are moving past.
- Stop oneloop.
- Install the new version:
cargo install --git https://github.com/code19m/oneloop --tag v0.1.0-rc.9 --locked sudo install ~/.cargo/bin/oneloop /usr/local/bin/oneloop - Upgrade the database, and check the result with the settings from the unit
file.
db migratesaves its backup in the backup folder from Back up:
If you changed the unit file, runsudo -u oneloop env $(systemctl show oneloop -p Environment --value) \ /usr/local/bin/oneloop db migrate --backup-dir /var/backups/oneloop sudo -u oneloop env $(systemctl show oneloop -p Environment --value) \ /usr/local/bin/oneloop serve --checksudo systemctl daemon-reloadfirst, so that the check reads the new settings. - Start oneloop.
With Docker, set up the backup folder as described in Back up, and
change the image tag in compose.yaml. Then run:
docker compose pull
docker compose stop
docker compose run --rm oneloop db migrate --backup-dir /backups
docker compose up -d
db migrate prints database in <data folder> migrated from 2 to 10 and the
folder of its backup, or database in <data folder> is current (schema 10) if
there was nothing to do.
Browser tabs that were open during the upgrade show oneloop was updated and reload by themselves when nothing typed would be lost; see Your account.
Choose a version
Stable versions look like 1.2.0. Release candidates, such as 1.2.0-rc.1,
let you try the next version early.
- The Docker tags
latestand0.1follow stable versions only. A release candidate gets only its exact tag. - In production, use an exact version, such as
ghcr.io/code19m/oneloop:0.1.0-rc.9or--tag v0.1.0-rc.9.
Databases from before the first release candidate
If you ran oneloop from source before its first release candidate, upgrade the
same way. db migrate makes a backup and converts the old database in one step.
It prints something like database in <data folder> migrated from 16 to 10.
Until you do this, oneloop serve refuses to start.
Roll back
Upgrades only go forward, and an older version refuses to open a newer database.
To go back, restore the backup that db migrate made:
- Stop oneloop. Move the data directory aside, or create a new Docker volume.
- Install the previous version, or set the previous image tag.
- Restore the backup that
db migrateprinted. Its name looks likeoneloop-pre-migration-v2-1790000000. - Start oneloop.
Changes made after the upgrade are not in that backup.