# Storage, backups & limits

Every Oleafly Server setting, storing files on disk or in an S3 bucket, running behind a proxy, limits for classes on one network, logs, backups, restores and updates.

This page covers running Oleafly Server day to day. To install it, start with [Run a collaboration server](/docs/collaboration/server/).

## Settings

The server reads its settings from environment variables. A variable that is set but empty counts as not set. If a setting is missing or malformed, the server doesn't start, and its log says which one.

| Variable | What it does | Default |
| --- | --- | --- |
| `OLEAFLY_PUBLIC_URL` | The HTTPS address people enter in Oleafly. It can include a path, such as `https://www.cs.example.edu/oleafly`. Required. The Compose and Coolify files set it for you. | None |
| `OLEAFLY_BIND` | The address and port the server listens on. | `0.0.0.0:8080` |
| `OLEAFLY_DATA_DIR` | The folder for the database and stored files. | `/data` |
| `OLEAFLY_SERVER_NAME` | The name people see in Oleafly, such as `Smith Lab`. | `Oleafly Server` |
| `OLEAFLY_SERVER_ICON_URL` | The HTTPS address of a square image shown next to the name. | None |
| `OLEAFLY_SETUP_CODE` | Your own one-time setup code: at least 12 letters or digits, with dashes or spaces if you like. Without it, the server makes one. | Generated |
| `OLEAFLY_GITHUB_CLIENT_ID`, `OLEAFLY_GITHUB_CLIENT_SECRET` | Turn on sign-in with GitHub. | Off |
| `OLEAFLY_GITHUB_URL` | The address of a GitHub Enterprise Server. | `https://github.com` |
| `OLEAFLY_GITHUB_API_URL` | The GitHub API address, if yours is unusual. | `https://api.github.com`, or `/api/v3` under `OLEAFLY_GITHUB_URL` |
| `OLEAFLY_GITHUB_LABEL` | The text on the GitHub sign-in button. | `GitHub` |
| `OLEAFLY_OIDC_<NAME>_ISSUER`, `OLEAFLY_OIDC_<NAME>_CLIENT_ID`, `OLEAFLY_OIDC_<NAME>_CLIENT_SECRET` | Turn on an OpenID Connect provider called `<NAME>`. | Off |
| `OLEAFLY_OIDC_<NAME>_LABEL` | The text on its sign-in button, such as `University login`. | `<NAME>` |
| `OLEAFLY_OIDC_<NAME>_TRUST_EMAIL` | Treat every email address from this provider as verified. Read [Verified email addresses](/docs/collaboration/server-access/#verified-email-addresses) first. | `false` |
| `OLEAFLY_SMTP_URL`, `OLEAFLY_MAIL_FROM` | Email. Read [Email](/docs/collaboration/server-access/#email). | Off |
| `OLEAFLY_BLOB_STORE` | Where files such as figures and PDFs are stored. `fs` keeps them in `OLEAFLY_DATA_DIR`. An address such as `s3://lab-bucket` or `s3://lab-bucket/oleafly` keeps them in a bucket. | `fs` |
| `OLEAFLY_S3_ENDPOINT` | The address of an S3-compatible store other than Amazon S3, such as MinIO or Cloudflare R2. Leave it empty for Amazon S3. | Amazon S3 |
| `OLEAFLY_S3_REGION` | The bucket's region. | `us-east-1` |
| `OLEAFLY_S3_ACCESS_KEY_ID`, `OLEAFLY_S3_SECRET_ACCESS_KEY` | The key the server uses for the bucket. Required with an `s3://` address. | None |
| `OLEAFLY_MAX_PROJECT_MB` | The largest project the server accepts, in megabytes. | `200` |
| `OLEAFLY_MAX_FILE_MB` | The largest single file, in megabytes. | `50` |
| `OLEAFLY_MAX_MEMBERS_PER_PROJECT` | The most people on one project. | `20` |
| `OLEAFLY_SIGN_IN_BURST` | How many sign-in requests one network address can make in a burst. A sign-in takes about six, so the default covers 50 people at one address signing in at once. | `300` |
| `OLEAFLY_SIGN_IN_PER_MINUTE` | How many sign-in requests one network address gets back each minute after the burst. | `120` |
| `OLEAFLY_EMAIL_LINKS_PER_IP` | How many sign-in emails the server sends to people at one network address in 15 minutes. | `100` |
| `OLEAFLY_EMAIL_LINKS_PER_ADDRESS` | How many sign-in emails one email address can get in 15 minutes. It can't be more than `OLEAFLY_EMAIL_LINKS_PER_IP`. | `3` |
| `OLEAFLY_DEVICE_CODES_PER_IP` | How many sign-in codes people at one network address can ask for in 10 minutes. | `100` |
| `OLEAFLY_SNAPSHOT_MEMORY_MB` | How much memory, in megabytes, the server may use to build project snapshots. | Half the container's memory limit, or `1024` without a limit |
| `OLEAFLY_BEHIND_PROXY` | Take each person's address from the `X-Forwarded-For` header your proxy adds. The Compose, Coolify and Render files turn it on. | `false` |
| `OLEAFLY_PROXY_HOPS` | How many proxies every request passes through before it reaches the server. It needs `OLEAFLY_BEHIND_PROXY=true`. | `1` |
| `OLEAFLY_LOG_LEVEL` | How much the server logs: `error`, `warn`, `info`, `debug` or `trace`. Libraries the server uses only log warnings unless you pick `debug` or `trace`. | `info` |
| `OLEAFLY_SECURITY_LOG_DAYS` | How many days to keep security log entries, project access log entries and answered or expired guest invitations. | `365` |
| `OLEAFLY_ARCHIVE_DAYS` | How many days to keep a project after its owner stops sharing it. | `30` |
| `OLEAFLY_POLL_HOLD_SECONDS` | How long the server keeps a long-polling request open when no edits are waiting, from 1 to 40 seconds. Keep it a few seconds under the idle timeout of any proxy in front of the server. | `25` |

In the Compose `.env` file, put a value in single quotes if it contains `$` or `#`. Settings that hold secrets can also be read from files. Read [Keep secrets out of .env](/docs/collaboration/server/#keep-secrets-out-of-env).

## Storage

By default the server keeps everything in `OLEAFLY_DATA_DIR`: the database, the files people share, and project snapshots. Snapshots are saved copies of each project that let people join and catch up quickly.

### Files in a bucket

The server can keep files and snapshots in Amazon S3 instead, or in another store with an S3-compatible API, such as MinIO or Cloudflare R2. The database stays in `OLEAFLY_DATA_DIR` either way, so the server still needs its data folder.

1. Create a bucket and block public access to it.
2. Create a key that can read, write and delete objects in that bucket and nothing else. On Amazon S3 that means `s3:GetObject`, `s3:PutObject` and `s3:DeleteObject` on `arn:aws:s3:::lab-bucket/*`, or on `arn:aws:s3:::lab-bucket/oleafly/*` if you use a folder.
3. Set the variables, for example in `.env`:

   ```sh
   OLEAFLY_BLOB_STORE=s3://lab-bucket/oleafly
   OLEAFLY_S3_REGION=eu-west-1
   OLEAFLY_S3_ACCESS_KEY_ID=your-access-key-id
   OLEAFLY_S3_SECRET_ACCESS_KEY=your-secret-access-key
   ```

For a store other than Amazon S3, also set `OLEAFLY_S3_ENDPOINT` to its address, such as `https://minio.example.org:9000` or `https://<account-id>.r2.cloudflarestorage.com`, and set `OLEAFLY_S3_REGION` to the region it expects (`auto` for R2). Plain `http://` works too, but use it only when the store sits on the same private network as the server.

The part after the bucket name is an optional folder. Each file is named after its SHA-256 hash and sits under the first two characters of that hash, so a file whose hash starts with `3f9a` is stored as `oleafly/3f/3f9a...`. Snapshots have their own `snapshots` folder with a folder for each project. When the server reads a file or snapshot back, it checks the bytes against the hash and refuses one that doesn't match. Without a bucket, the same layout is in the `blobs` folder inside `OLEAFLY_DATA_DIR`.

If the bucket refuses a request, that upload or download fails, and the server log shows what the bucket said, such as `403 Forbidden (AccessDenied)`. A server error, a timeout or a dropped connection is different: the server tries up to three times, with a short random pause in between. If it can't delete an object, it notes that and tries again at each maintenance pass, every 6 hours, so an outage doesn't leave objects behind.

### Snapshots and memory

When a project is shared, the owner's app uploads its first snapshot and signs it with that device's key. From then on the server builds every snapshot itself, from the previous snapshot and the changes people sent since. A new snapshot is built once a project has 1,000 changes past the last one. The server always keeps at least the last 2,000 changes, so a device that was away for a short time catches up from those and doesn't download the whole project again.

Building a snapshot needs about ten times the size of the previous snapshot plus the changes since it. The server only starts a build that fits in `OLEAFLY_SNAPSHOT_MEMORY_MB`, and builds running at the same time share that amount. If a build doesn't fit, the server skips it, logs how much memory it needs and keeps all the project's changes. Devices then catch up from those changes a few megabytes at a time. Raise the setting and the next snapshot gets built. The server won't store a snapshot larger than `OLEAFLY_MAX_PROJECT_MB`.

## Behind a reverse proxy

The server speaks plain HTTP, so something in front of it handles HTTPS: Caddy in the Compose setup, Coolify's proxy, or a web server you already run, such as your university's. Whatever sits in front has to:

- accept requests as large as `OLEAFLY_MAX_PROJECT_MB`
- add the client's address to `X-Forwarded-For`

It should also pass WebSocket upgrades through to `/ws` and keep those connections open for at least an hour. Live editing uses them when it can.

Run the server where only the proxy can reach it. On the same machine, set `OLEAFLY_BIND=127.0.0.1:8080`. Then set `OLEAFLY_BEHIND_PROXY=true`.

### When WebSocket connections are blocked

Some networks block WebSocket connections, often a university or company network with a filtering proxy. Live editing still works there, more slowly. When two WebSocket attempts in a row fail while the server still answers over HTTPS, Oleafly switches to HTTP long-polling. It sends edits to `/api/v1/projects/<project>/frames` and waits for other people's edits on `/api/v1/projects/<project>/poll`, holding each request open for up to `OLEAFLY_POLL_HOLD_SECONDS`. Every edit still arrives. Oleafly tries a WebSocket again every minute and goes back to it as soon as one connects.

**Test connection** in Oleafly and **Connection checks** in the admin console say when this happens: "Live edits take a slower route because this network blocks the usual one. They still arrive."

If you run the proxy in front of the server, pass WebSocket upgrades through anyway. Long-polling works, but each batch of edits costs an extra request and a little more delay.

Some proxies close a request that stays quiet for less than 30 seconds, nginx with a short `proxy_read_timeout` for one. They answer long-polling requests with a 502 or 504 error, and edits arrive late. Set `OLEAFLY_POLL_HOLD_SECONDS` a few seconds under that timeout, such as `8` for a 10-second timeout.

### What OLEAFLY_BEHIND_PROXY changes

The server notes a client address in two places: the security log, and the [limits](#limits-for-a-class-on-one-network) on how often one address can start a sign-in or open a live connection.

Without the setting, the server uses the address of whatever connected to it. Behind a proxy that's always the proxy, so everyone shares one limit and the security log shows only the proxy's address.

With `OLEAFLY_BEHIND_PROXY=true`, the server reads the `X-Forwarded-For` header and uses the last address in it, which is the one your proxy added. If the header is missing or holds no valid address, the server falls back to the connecting address. Nothing else changes. The server ignores `X-Forwarded-Proto`, `X-Forwarded-Host` and `Forwarded`, and builds every address it hands out from `OLEAFLY_PUBLIC_URL`.

Turn it on only when every request comes through your proxy. If people can also reach the server directly, anyone can send a made-up `X-Forwarded-For`, put any address in the security log and get around the sign-in limit.

With two proxies in a row, for example Cloudflare in front of Coolify's proxy, set `OLEAFLY_PROXY_HOPS=2`. The server then takes the second address from the end. Count only the proxies that every request goes through. A Cloudflare Tunnel that leads straight to the server counts as one.

To check the number, sign in from your own computer and open **Security log** in the admin console. The newest entry should show your public address, the one a site such as `ifconfig.me` reports. If it shows a private or Cloudflare address instead, raise `OLEAFLY_PROXY_HOPS` by one.

### Caddy

Caddy passes WebSocket connections and `X-Forwarded-For` through without extra settings, and gets the certificate itself. [Put HTTPS in front](/docs/collaboration/server/#put-https-in-front) has the whole `Caddyfile`.

### nginx

Get a certificate for the domain first, for example with certbot. Then save this as `/etc/nginx/conf.d/oleafly.conf` and run `sudo nginx -t && sudo systemctl reload nginx`:

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    server_name collab.example.org;
    ssl_certificate /etc/letsencrypt/live/collab.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/collab.example.org/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;
        client_max_body_size 200m;
    }
}
```

`client_max_body_size` matches the default `OLEAFLY_MAX_PROJECT_MB` of 200. If you raise one, raise the other.

### Apache

You need Apache 2.4.47 or newer, which Debian 12 and Ubuntu 22.04 and later have. Turn on the modules:

```sh
sudo a2enmod proxy proxy_http ssl
```

Save this as `/etc/apache2/sites-available/oleafly.conf`, then run `sudo a2ensite oleafly && sudo systemctl reload apache2`:

```apache
<VirtualHost *:443>
    ServerName collab.example.org
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/collab.example.org/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/collab.example.org/privkey.pem

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/ upgrade=websocket timeout=3600
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>
```

`upgrade=websocket` passes WebSocket connections through and `timeout=3600` keeps them open for an hour. Apache adds the client's address to `X-Forwarded-For` by itself.

### Under a path

If the server lives under a path, include the path in `OLEAFLY_PUBLIC_URL`, for example `https://www.cs.example.edu/oleafly`, and in the callback addresses you register with sign-in providers. The server answers both with and without that path, so your proxy can pass it on or strip it. With nginx, use `location /oleafly/ {` with the same lines inside, and keep `proxy_pass http://127.0.0.1:8080;` without a path so nginx passes the path on.

## Limits for a class on one network

A whole class often signs in from one network address, such as a university's NAT. The defaults are set for that:

- **Sign-ins**: one address gets a burst of 300 sign-in requests, then two a second. A sign-in takes about six, so 50 people can sign in at once. Change these with `OLEAFLY_SIGN_IN_BURST` and `OLEAFLY_SIGN_IN_PER_MINUTE`.
- **Email links**: 100 to one address in 15 minutes, and 3 to one email address. Change these with `OLEAFLY_EMAIL_LINKS_PER_IP` and `OLEAFLY_EMAIL_LINKS_PER_ADDRESS`.
- **Sign-in codes**: 100 codes, and 10 wrong codes, from one address in 10 minutes. Change this with `OLEAFLY_DEVICE_CODES_PER_IP`.
- **Live connections** have their own, larger limit, so a whole class behind one address can reconnect at once after a restart. Only connections that fail to prove their session use it up: a burst of 300, then ten a second. A connection that proves its session counts against its own device instead, which can reconnect 60 times in a burst, then once a second.

All IPv6 addresses in one `/64` network count as one address. These limits only work per person when the server sees each person's real address, so [set up your proxy](#what-oleafly_behind_proxy-changes) correctly.

The server pings each live connection every 30 seconds. It closes a connection that leaves those pings unanswered for 90 seconds, and one that stops reading what the server sends.

Project limits apply to everyone: `OLEAFLY_MAX_PROJECT_MB` for a whole project (200 MB by default), `OLEAFLY_MAX_FILE_MB` for one file (50 MB) and `OLEAFLY_MAX_MEMBERS_PER_PROJECT` for people on one project (20). The admin console's **Overview** shows the current limits.

## Logs

- **The server log** is what `docker compose logs oleafly-server`, Coolify's **Logs** or `journalctl -u oleafly-server` show. `OLEAFLY_LOG_LEVEL` sets how much it says.
- **The security log** records sign-ins and refused sign-ins, devices, people, roles, settings, projects and guests, each with the time and the IP address. Admins and maintainers read it under **Security log** in the admin console. Entries older than `OLEAFLY_SECURITY_LOG_DAYS` days are deleted.
- **Project access logs** record who opened a project, joined it or downloaded a copy, with the device name and the time. A project's owner turns one on, when **Allow project access logs** is on, and only that owner can read it. The same retention applies.

## Backups

What a full backup needs depends on where files are stored:

- **Files on disk** (`OLEAFLY_BLOB_STORE=fs`, the default): the database, files and snapshots are all in the data folder. Back up the whole folder.
- **Files in a bucket**: the data folder holds the database and the bucket holds files and snapshots. A copy of the database plus the bucket is a full backup. Turn on versioning for the bucket so an object deleted by mistake can be brought back.

Back up regularly and keep the copies private, since they hold every project. Your settings, such as `.env`, aren't in the data folder, so keep a copy of them as well.

With Docker, the server stops for a few seconds while you copy the volume. Oleafly reconnects by itself. Run these commands in the server's folder, `/opt/oleafly-server` if you used the installer.

```sh
cd /opt/oleafly-server
sudo docker compose stop oleafly-server
sudo docker run --rm -v oleafly-server_oleafly-data:/data:ro -v "$PWD":/backup debian:13.7-slim tar -C /data -czf /backup/oleafly-data-$(date +%F).tar.gz .
sudo docker compose start oleafly-server
```

With systemd:

```sh
sudo systemctl stop oleafly-server
sudo tar -C /var/lib/private/oleafly-server -czf /root/oleafly-data-$(date +%F).tar.gz .
sudo systemctl start oleafly-server
```

Copy the archive to another machine.

On Coolify, the volume's name starts with the resource's ID. Find it with `docker volume ls --filter name=oleafly-data`, use it in place of `oleafly-server_oleafly-data`, and stop and start the server from Coolify's dashboard instead of with `docker compose`.

### Restore drill

A backup you've never restored is only a hope. This drill starts a second copy of the server from a backup, leaves the running one alone, and checks that both are the same server. Use the date of your own archive in place of `2026-10-11`.

```sh
cd /opt/oleafly-server
sudo docker volume create oleafly-restore-drill
sudo docker run --rm -v oleafly-restore-drill:/data -v "$PWD":/backup:ro debian:13.7-slim tar -C /data -xzf /backup/oleafly-data-2026-10-11.tar.gz
sudo docker run -d --name oleafly-restore-drill -v oleafly-restore-drill:/data -e OLEAFLY_PUBLIC_URL=https://restore-drill.invalid -e OLEAFLY_GITHUB_CLIENT_ID=restore-drill -e OLEAFLY_GITHUB_CLIENT_SECRET=restore-drill "$(sudo docker compose images -q oleafly-server)"
sudo docker run --rm --network container:oleafly-restore-drill curlimages/curl:8.22.0 -fsS --retry 10 --retry-connrefused --retry-delay 1 http://127.0.0.1:8080/.well-known/oleafly-server
curl -fsS https://collab.example.org/.well-known/oleafly-server
```

The `server_id` in the two answers must match. The drill copy has a placeholder GitHub sign-in that can't sign anyone in, and no open port, so nobody can use it. Remove it when you're done:

```sh
sudo docker rm -f oleafly-restore-drill
sudo docker volume rm oleafly-restore-drill
```

### Restore

To restore for real with Docker Compose, stop the server, replace the volume's contents with the archive, and start it again:

```sh
cd /opt/oleafly-server
sudo docker compose stop oleafly-server
sudo docker run --rm -v oleafly-server_oleafly-data:/data -v "$PWD":/backup:ro debian:13.7-slim sh -c 'find /data -mindepth 1 -delete && tar -C /data -xzf /backup/oleafly-data-2026-10-11.tar.gz'
sudo docker compose start oleafly-server
```

With systemd:

```sh
sudo systemctl stop oleafly-server
sudo find /var/lib/private/oleafly-server -mindepth 1 -delete
sudo tar -C /var/lib/private/oleafly-server -xzf /root/oleafly-data-2026-10-11.tar.gz
sudo systemctl start oleafly-server
```

A restored server is missing the changes people made after the backup. Each app notices when it reconnects, whether it stayed open or was opened later, and sends its own missing changes again. Nobody has to do anything for that. A change that builds on someone else's missing change waits until that person opens the project, because only their app can send their changes. Ask everyone who was editing around the time of the failure to open the project once.

### Continuous copies with Litestream

An archive only holds what was there when you made it. [Litestream](https://litestream.io) copies each change to the server's database to an S3-compatible bucket about a second after it happens. The Compose file includes it as an optional service.

Litestream copies the database only. If files are in a bucket, the bucket already holds files and snapshots, so the Litestream copy and the bucket together can bring back a lost disk. If files are on disk, keep making archives of the volume as well, because Litestream doesn't copy the `blobs` folder.

1. Create a bucket, or a folder in one, that only this server's Litestream uses. Don't share it with `OLEAFLY_BLOB_STORE`. Give Litestream a key that can list the bucket and read, write and delete objects in it.
2. Add the details to `.env`:

   ```sh
   LITESTREAM_REPLICA_URL=s3://lab-backups/oleafly-server
   LITESTREAM_REGION=eu-west-1
   LITESTREAM_ACCESS_KEY_ID=your-access-key-id
   LITESTREAM_SECRET_ACCESS_KEY=your-secret-access-key
   COMPOSE_PROFILES=litestream
   ```

   For a store other than Amazon S3, also set `LITESTREAM_ENDPOINT` to its address and `LITESTREAM_REGION` to the region it expects (`auto` for R2). `COMPOSE_PROFILES` makes every `docker compose` command include Litestream.
3. Start it:

   ```sh
   sudo docker compose up -d
   ```

4. Check that it copied the database. This prints a line with `snapshot complete`:

   ```sh
   sudo docker compose logs litestream | grep 'snapshot complete'
   ```

Litestream takes a full copy once a day and keeps a week of copies and the changes between them. The schedule is in `litestream.yml`.

#### Litestream restore drill

This drill restores the database from the bucket into a scratch volume, starts a second copy of the server on it, and checks that both are the same server. The running server isn't touched.

```sh
cd /opt/oleafly-server
sudo docker run --rm -v oleafly-litestream-drill:/data "$(sudo docker compose images -q oleafly-server)" --version
sudo docker compose run --rm --no-deps -v oleafly-litestream-drill:/restore litestream restore -config /etc/litestream.yml -o /restore/oleafly-server.sqlite3 /data/oleafly-server.sqlite3
sudo docker run -d --name oleafly-litestream-drill -v oleafly-litestream-drill:/data -e OLEAFLY_PUBLIC_URL=https://restore-drill.invalid -e OLEAFLY_GITHUB_CLIENT_ID=restore-drill -e OLEAFLY_GITHUB_CLIENT_SECRET=restore-drill "$(sudo docker compose images -q oleafly-server)"
sudo docker run --rm --network container:oleafly-litestream-drill curlimages/curl:8.22.0 -fsS --retry 10 --retry-connrefused --retry-delay 1 http://127.0.0.1:8080/.well-known/oleafly-server
curl -fsS https://collab.example.org/.well-known/oleafly-server
```

The first command creates the scratch volume with the owner the server needs. The `server_id` in the last two answers must match. Remove the copy when you're done:

```sh
sudo docker rm -f oleafly-litestream-drill
sudo docker volume rm oleafly-litestream-drill
```

#### Restore the database from Litestream

When the database file is damaged or gone but the rest of the data folder is fine, stop the server and Litestream, remove what's left of the database, restore it and start again:

```sh
cd /opt/oleafly-server
sudo docker compose stop oleafly-server litestream
sudo docker run --rm -v oleafly-server_oleafly-data:/data debian:13.7-slim rm -rf /data/oleafly-server.sqlite3 /data/oleafly-server.sqlite3-wal /data/oleafly-server.sqlite3-shm /data/.oleafly-server.sqlite3-litestream
sudo docker compose run --rm --no-deps litestream restore -config /etc/litestream.yml /data/oleafly-server.sqlite3
sudo docker compose up -d
```

After a lost disk with files in a bucket, run the commands above on the new, empty volume and keep the same bucket settings in `.env`. Every project comes back with its files and snapshots.

After a lost disk with files on disk, [restore](#restore) your newest archive and leave it at that. Don't put a newer database from Litestream on top of an older archive. The database would point at files and snapshots the archive doesn't have, and those projects wouldn't open.

## Updating

Back up first. The server updates its database the first time a new release starts, so going back to an older release means restoring the backup you took before updating.

- **One-command install**: run `curl -fsSL https://oleafly.com/server/install.sh | sudo sh -s -- update`. It keeps `.env`, gets the new release's files and image, and restarts the server.
- **Docker Compose by hand**: download the new release's files into the server's folder as in [Docker Compose by hand](/docs/collaboration/server/#docker-compose-by-hand), then run `sudo docker compose up -d`.
- **Coolify**: select **Redeploy**. With the published image, that pulls the newest release under the image's tag.
- **Render**: open the service and start a manual deploy, which pulls the image again.
- **Railway**: deploy the service again from its menu. Railway can also update an image from GitHub's registry by itself.
- **systemd**: install the new release as in [Download the server](/docs/collaboration/server/#download-the-server), then run `sudo systemctl restart oleafly-server`.

Oleafly checks the server's version every time it connects. If the app and the server can't work together, Oleafly says which one needs an update.
