# Run a collaboration server

What Oleafly Server stores, what it needs, and how to install it with one command, or run it with Docker Compose, Coolify, Render, Railway or a plain Linux service, then become its first admin.

Oleafly Server is the collaboration server behind live collaboration. Your lab, department or institution runs it on its own machines or in its own cloud account. People add it in Oleafly and sign in with an account they already have, such as GitHub or a university login. When someone shares a project on the server, the people they add get a full copy that stays in sync while they edit.

This guide is for the person who runs the server for a lab, a course or a group of co-authors. You should be comfortable with a terminal. Some setups also ask you to edit a configuration file. You don't need to write code.

For your security review, read [Server security](/security/). It covers self-hosting, sign-in, device keys, signed changes, the security and access logs, and the container image with its software bill of materials.

## What the server keeps

The server keeps everything in one data folder, set by `OLEAFLY_DATA_DIR`:

- **Accounts**: each member's display name, avatar address, verified email addresses, and their user ID and login at the sign-in provider. The server never sees or stores a password.
- **Devices**: each computer that signed in, with its name, operating system, public key and when it was last seen. Sign-in tokens are stored only as hashes.
- **The security log**: sign-ins and refused sign-ins, devices added and signed out, changes to members, roles and settings, projects shared, archived and restored, changes to who can open a project, and guest invitations. Each entry has the time and the IP address.
- **Server settings** that admins change in Oleafly.
- **Project access logs**, for projects whose owner turns one on: who opened the project, joined it or downloaded a copy, with the device name and the time. Only the project's owner can read it.
- **Guest invitations**: the invited email address, the project, the role and who sent it.
- **Shared projects**: every file and the full history of changes.

Entries in the security log and access logs, and answered or expired guest invitations, are deleted after `OLEAFLY_SECURITY_LOG_DAYS` days, 365 by default. Files a project no longer uses, such as the old copy of a replaced figure, are deleted 30 days after the project last used them. When an owner stops sharing a project, the server keeps it read-only for `OLEAFLY_ARCHIVE_DAYS` days, 30 by default, and then deletes it. In that time only the owner and any admins in the project can download it.

### Who can read projects

Projects are not encrypted on the server. Anyone who can read the data folder, the machine's disk or your backups can read every project stored there. That includes whoever runs the machine and the company that hosts it.

Inside Oleafly the rules are stricter. No server role, admin included, can open a project unless the project's owner added them. If a project must stay away from the people who run a server, run a separate server for it.

Every member's computer also keeps a full copy of each project shared with them. Ask members to turn on disk encryption, such as FileVault on macOS, BitLocker on Windows or LUKS on Linux.

## What you need

- **A machine that stays on.** Any Linux machine works, or an account with a hosting service such as Render or Railway. A small virtual machine is enough to start. Watch its memory and disk as projects grow. The server runs on x86-64 and ARM.
- **A domain name** for the server, such as `collab.example.org`, with a DNS record pointing at the machine. Render and Railway give you an address if you don't have one. If the machine has no public address, read [Without a public address](#without-a-public-address).
- **A way for people to sign in**: at least one sign-in provider, such as a GitHub OAuth app or your institution's login. The server won't start without one. An email account the server can send from is optional. With it, an admin can also let people sign in with an emailed link. Read [Sign-in and access](/docs/collaboration/server-access/).

Each release is published as a container image, `ghcr.io/oleafly/oleafly-server`. The installer and its Compose files are on the [Oleafly Server releases page](https://github.com/Oleafly/oleafly-server/releases). The server as a single Linux program is on the [Oleafly releases page](https://github.com/Oleafly/Oleafly/releases).

## Choose how to run it

| Option | When to use it |
| --- | --- |
| [One command](#install-with-one-command) | A Linux machine you look after. The installer sets up Docker, Caddy and HTTPS, and asks a few questions. Start here if you're not sure. |
| [Docker Compose by hand](#docker-compose-by-hand) | You'd rather place and edit each file yourself. |
| [Coolify](#coolify) | You already run a Coolify server. |
| [Render](#render) or [Railway](#railway) | You'd rather pay a hosting service than look after a machine. |
| [Linux without Docker](#linux-without-docker) | A university virtual machine where you can't use Docker. |

The server reads its settings from environment variables. [Storage, backups & limits](/docs/collaboration/server-operations/#settings) lists every one of them.

## Install with one command

On a Linux machine you look after yourself, the installer sets everything up and asks only what it needs to know.

Before you run it, check that:

- The machine runs Linux on x86-64 or ARM, such as Ubuntu 24.04 or Debian 13, and you can use `sudo` on it.
- Nothing else on the machine uses ports 80 and 443. Open TCP ports 80 and 443 and UDP port 443 in any firewall in front of it.
- Your domain's DNS record points at the machine. If it doesn't yet, the install still finishes, and Caddy keeps trying to get a certificate until the record is in place.
- You have the details of at least one [sign-in provider](/docs/collaboration/server-access/#sign-in-providers), such as a GitHub OAuth app's client ID and secret. For GitHub, the callback address is `https://` and your domain, followed by `/auth/callback/github`.

:::note[During the beta]
Install the release candidate, Oleafly Server 0.1.1-rc, so the server matches Oleafly 0.4.7-rc:

```sh
curl -fsSL https://github.com/Oleafly/oleafly-server/releases/download/server-v0.1.1-rc/install.sh | sudo sh
```

The command below installs 0.1.0, which is older. If you run the image another way, such as with Compose, Coolify, Render or Railway, use `ghcr.io/oleafly/oleafly-server:0.1.1-rc`.
:::

Then run:

```sh
curl -fsSL https://oleafly.com/server/install.sh | sudo sh
```

The installer:

1. Checks the machine, and that ports 80 and 443 are free.
2. Checks for Docker. If it's missing, the installer asks before installing it with Docker's own script.
3. Downloads the newest release's files from the [Oleafly Server releases](https://github.com/Oleafly/oleafly-server/releases) and checks each one against the release's `SHA256SUMS`. If a file doesn't match, it stops without changing anything.
4. Asks for your domain, the server's name and how people sign in. It also offers to set up email and to keep files in an S3 bucket. Both are optional.
5. Shows a summary and asks before it changes anything. Then it writes its files to `/opt/oleafly-server`, with your settings in `.env`, which only root can read.
6. Starts the server with Caddy in front, waits until your domain answers over HTTPS, and prints the address and the setup code.

Then [become the first admin](#become-the-first-admin).

You can run the installer again at any time. When it finds a server in `/opt/oleafly-server`, it keeps `.env` as it is, puts back the other files and restarts the server. That repairs most broken setups.

Your data lives in the Docker volume `oleafly-server_oleafly-data`. To install somewhere other than `/opt/oleafly-server`, set `OLEAFLY_DIR`, for example `curl -fsSL https://oleafly.com/server/install.sh | sudo OLEAFLY_DIR=/srv/oleafly sh`, and use that folder in the commands below.

### Read the installer first

If your institution doesn't allow piping a script into a shell, download `install.sh` and `SHA256SUMS` from the [latest release](https://github.com/Oleafly/oleafly-server/releases/latest) into one folder. Check the script, read it, then run it:

```sh
sha256sum --check --ignore-missing SHA256SUMS
sudo sh install.sh
```

### Install without questions

For a script or a provisioning tool, add `--yes` and pass the answers as environment variables. `OLEAFLY_INSTALL_DOCKER=yes` lets the installer add Docker if it's missing.

```sh
curl -fsSL https://oleafly.com/server/install.sh | sudo \
  OLEAFLY_DOMAIN=collab.example.org \
  OLEAFLY_SERVER_NAME='Smith Lab' \
  OLEAFLY_GITHUB_CLIENT_ID=Ov23liExampleClientId \
  OLEAFLY_GITHUB_CLIENT_SECRET=example-client-secret \
  OLEAFLY_INSTALL_DOCKER=yes \
  sh -s -- --yes
```

The installer uses any setting it finds in the environment without asking, with or without `--yes`. `sudo sh /opt/oleafly-server/install.sh --help` lists them all, including OpenID Connect, email and S3. Values typed on a command line can end up in your shell history, so a provisioning tool should pass secrets from its own secret store.

### Status, updates and removal

- **Status**: `sudo sh /opt/oleafly-server/install.sh status` shows whether the server is running and whether your domain answers over HTTPS with a valid certificate.
- **Update**: back up first, as [Updating](/docs/collaboration/server-operations/#updating) explains. Then run the command below. It downloads the newest release's files and image, keeps `.env`, restarts the server and waits until it answers. People who are connected reconnect by themselves. The installer never moves a server to an older release.

  ```sh
  curl -fsSL https://oleafly.com/server/install.sh | sudo sh -s -- update
  ```

- **Uninstall**: `sudo sh /opt/oleafly-server/install.sh uninstall` stops and removes the containers. Your data and `.env` stay unless you type `delete` when it asks. Deleting them removes every shared project on the server, every account and the security log. People keep their own copies of projects on their computers.

If the installer stops with a problem you can't fix, [report it](https://github.com/Oleafly/oleafly-server/issues/new/choose) with what it printed.

## Docker Compose by hand

This setup runs two containers. `oleafly-server` is the server, and only other containers can reach it. `caddy` gets a certificate from Let's Encrypt, serves HTTPS on ports 80 and 443, and passes requests to the server, live connections included.

1. Install Docker Engine and the Compose plugin by following [Docker's instructions](https://docs.docker.com/engine/install/).
2. Point your domain's DNS record at the machine. Open TCP ports 80 and 443 and UDP port 443 in any firewall in front of it. Caddy needs port 80 to get the certificate.
3. Make a folder for the server, download the newest release's files into it, check them, and make your settings file:

   ```sh
   sudo mkdir -p /opt/oleafly-server && cd /opt/oleafly-server
   base=https://github.com/Oleafly/oleafly-server/releases/latest/download
   for f in docker-compose.yml Caddyfile env.example litestream.yml SHA256SUMS; do sudo curl -fsSLO "$base/$f"; done
   sha256sum --check --ignore-missing SHA256SUMS
   sudo install -m 600 env.example .env
   ```

4. Edit `.env` with `sudoedit .env`. Set `OLEAFLY_DOMAIN` to your domain without `https://`, set `OLEAFLY_SERVER_NAME`, and fill in at least one [sign-in provider](/docs/collaboration/server-access/#sign-in-providers). Leave the provider lines you don't use empty. Put a value in single quotes if it contains `$` or `#`.
5. Start it. The first start downloads the server image, `ghcr.io/oleafly/oleafly-server`, at the release's version.

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

6. Check that it answers. This prints `200`:

   ```sh
   curl -sS -o /dev/null -w '%{http_code}\n' https://collab.example.org/healthz
   ```

7. [Become the first admin](#become-the-first-admin). The setup code is in the log:

   ```sh
   sudo docker compose logs oleafly-server | grep 'Setup code'
   ```

Your data lives in the Docker volume `oleafly-server_oleafly-data`. Caddy keeps its certificates in `oleafly-server_caddy-data`.

Docker opens ports 80 and 443 itself, even when a host firewall such as `ufw` is on. That's what Caddy needs. Don't add a `ports:` entry to the `oleafly-server` service. To reach the server from the machine itself, publish it as `127.0.0.1:8080:8080` so it stays off the network.

If you change the `Caddyfile`, run `sudo docker compose up -d --force-recreate caddy`. To update later, download the new release's files the same way and run `sudo docker compose up -d` again.

### Keep secrets out of .env

Seven settings can hold secrets: `OLEAFLY_SETUP_CODE`, `OLEAFLY_GITHUB_CLIENT_SECRET`, `OLEAFLY_OIDC_<NAME>_CLIENT_SECRET`, `OLEAFLY_SMTP_URL`, `OLEAFLY_BLOB_STORE`, `OLEAFLY_S3_ACCESS_KEY_ID` and `OLEAFLY_S3_SECRET_ACCESS_KEY`. Each one also has a `_FILE` version that names a file holding the value. Set one of the two, not both.

For example, to pass the GitHub client secret as a Docker secret, save it in `secrets/github_client_secret` next to `docker-compose.yml`, let the server's user (ID 10001) read it, and delete the `OLEAFLY_GITHUB_CLIENT_SECRET` line from `.env`:

```sh
sudo chown 10001:10001 secrets/github_client_secret
sudo chmod 400 secrets/github_client_secret
```

Then create `docker-compose.override.yml` next to `docker-compose.yml`. Compose reads it automatically, and the installer leaves it alone when it updates the other files.

```yaml
services:
  oleafly-server:
    environment:
      OLEAFLY_GITHUB_CLIENT_SECRET_FILE: /run/secrets/github_client_secret
    secrets:
      - github_client_secret

secrets:
  github_client_secret:
    file: ./secrets/github_client_secret
```

Run `sudo docker compose up -d` again.

## Coolify

Coolify puts its own proxy in front of the server and gets the certificate, so this setup has no Caddy. Coolify can build the server from the Oleafly repository, or run the [published image](#deploy-the-published-image), which skips the build.

Before you start, point your domain's DNS record, such as `collab.example.org`, at the Coolify server. Then create the GitHub OAuth app as described under [GitHub](/docs/collaboration/server-access/#github), with the callback address `https://collab.example.org/auth/callback/github`. Keep its client ID and secret at hand.

### Build from the repository

1. In your Coolify project, select **+ New**, then **Public Repository**, and enter `https://github.com/Oleafly/Oleafly`. Keep the branch `main`.
2. Set **Build Pack** to **Docker Compose**, **Base Directory** to `/` and **Docker Compose Location** to `/deploy/server/docker-compose.coolify.yml`, then select **Continue**.
3. Under **Advanced › Build**, set **Build arguments** to **Managed manually in Dockerfile**. The server's image needs no build arguments. Left on automatic, Coolify writes every variable, secrets included, into the image's build history.
4. Under **General**, set the domain of `oleafly-server` to `https://collab.example.org:8080` and save. The `:8080` tells Coolify's proxy which port the server listens on. People still connect on the usual HTTPS port, and Coolify sets `OLEAFLY_PUBLIC_URL` to `https://collab.example.org` for you.
5. Under **Environment Variables**, fill in `OLEAFLY_SERVER_NAME`, which Coolify marks as required, then `OLEAFLY_GITHUB_CLIENT_ID` and `OLEAFLY_GITHUB_CLIENT_SECRET`. Leave the variables you don't use empty.
6. Select **Deploy**. The first build compiles the server and takes a few minutes. When it's done, Coolify shows the resource as running and healthy.
7. Open `https://collab.example.org/healthz` in a browser. It shows `{"status":"ok"}`.
8. Open **Logs**, choose the `oleafly-server` container, and copy the code from the newest line that starts with `Setup code:`.
9. [Become the first admin](#become-the-first-admin) with that code.

If the server doesn't start, its log names the setting it can't use. When it says `OLEAFLY_PUBLIC_URL` is not set, step 4 is missing. A domain that starts with `http://` is refused, because the server needs HTTPS.

If Cloudflare sits in front of Coolify, because your DNS record goes through Cloudflare's proxy or a tunnel leads to Coolify's proxy, set `OLEAFLY_PROXY_HOPS` to `2`. Without it, the security log shows the proxy's address for everyone, and they all share one sign-in limit.

The Coolify file has room for GitHub and one OpenID Connect provider named `UNIVERSITY`. Set `OLEAFLY_OIDC_UNIVERSITY_LABEL` to the text people should see, such as `GitLab`. Its callback address ends in `/auth/callback/university`. For more providers, deploy from a fork with your own copy of the file.

The data lives in a Docker volume whose name ends in `oleafly-data`. Find it with `docker volume ls --filter name=oleafly-data`. The image has no shell, so Coolify's **Terminal** can't open one in the server's container. Logs and the health check work as usual.

### Deploy the published image

Running the published image skips the build, which suits a small Coolify server. Follow the steps above with two changes. In step 2, set **Docker Compose Location** to `/deploy/server/docker-compose.coolify-image.yml`. Skip step 3, because nothing gets built. The file follows one release series, so each **Redeploy** pulls the newest release in it.

## Render

[Deploy to Render](https://render.com/deploy?repo=https://github.com/Oleafly/Oleafly) reads the Blueprint in the Oleafly repository. It creates one web service from the published image, with a 1 GB disk for the data folder. Render attaches disks only to paid instance types, so the service starts on the smallest paid one, in Render's Oregon region. For another region, deploy from a fork with `region` set in `render.yaml`.

1. Open the link and sign in to Render.
2. Render asks for `OLEAFLY_SERVER_NAME` and the GitHub client ID and secret. Fill in the name and leave the GitHub fields empty for now. Their callback address depends on the service's address, which you don't have yet.
3. Select **Deploy Blueprint**. Once the service is live, its page shows its address, such as `https://oleafly-server.onrender.com`. The Blueprint sets `OLEAFLY_PUBLIC_URL` to that address.
4. Create the GitHub OAuth app as described under [GitHub](/docs/collaboration/server-access/#github), with that address followed by `/auth/callback/github` as the callback address. Add the client ID and secret under the service's **Environment**. Render restarts the server with them.
5. Find the newest `Setup code:` line in the service's **Logs** and [become the first admin](#become-the-first-admin).

If you give the service your own domain, set `OLEAFLY_PUBLIC_URL` to it under **Environment** and change the callback address at GitHub to match.

Because of the disk, Render stops the running server before it starts the new one, so each deploy takes the server away for a few seconds. Oleafly reconnects by itself.

## Railway

Railway runs the published image, with a volume for the data folder.

1. In a Railway project, select **+ New**, then **Docker Image**, and enter `ghcr.io/oleafly/oleafly-server:latest`. To choose when you upgrade, use a release's own tag from the [releases page](https://github.com/Oleafly/Oleafly/releases) instead. Railway may start a first deploy straight away. It stops with `OLEAFLY_PUBLIC_URL is not set`, which is expected at this point.
2. Open the service. Under **Settings › Networking**, select **Generate Domain** and enter port `8080`. Railway shows the address, such as `oleafly-server-production.up.railway.app`.
3. Under **Settings › Deploy**, set **Healthcheck Path** to `/readyz`.
4. Right-click the project canvas, add a volume, attach it to the server's service and set its mount path to `/data`.
5. Under **Variables**, open the **Raw Editor** and paste these lines with your own server name. For the GitHub values, follow [GitHub](/docs/collaboration/server-access/#github). The callback address is your address from step 2 with `https://` in front and `/auth/callback/github` after it.

   ```ini
   OLEAFLY_PUBLIC_URL=https://${{RAILWAY_PUBLIC_DOMAIN}}
   OLEAFLY_BIND=0.0.0.0:8080
   OLEAFLY_DATA_DIR=/data
   OLEAFLY_BEHIND_PROXY=true
   OLEAFLY_SERVER_NAME=Smith Lab
   OLEAFLY_GITHUB_CLIENT_ID=Ov23liExampleClientId
   OLEAFLY_GITHUB_CLIENT_SECRET=example-client-secret
   PORT=8080
   RAILWAY_RUN_UID=0
   ```

   Railway mounts volumes as root. `RAILWAY_RUN_UID=0` runs the server as root so it can write to the volume.

6. Deploy the changes. Find the newest `Setup code:` line in the service's **Deploy Logs** and [become the first admin](#become-the-first-admin).

## Linux without Docker

On a machine without Docker, the server runs as a single program under systemd, with Caddy in front for HTTPS.

### Download the server

Each server release on the [releases page](https://github.com/Oleafly/Oleafly/releases) has a ready-built program for x86-64 and for ARM, and a `SHA256SUMS` file to check it against. The program needs nothing else installed.

Download the archive for your machine, ending in `x86_64-unknown-linux-musl.tar.gz` or `aarch64-unknown-linux-musl.tar.gz`, and `SHA256SUMS` into one folder. Then check and install it. On an ARM machine, use `aarch64` in place of `x86_64`:

```sh
sha256sum --check --ignore-missing SHA256SUMS
tar -xzf oleafly-server-*-x86_64-unknown-linux-musl.tar.gz
sudo install -m 0755 oleafly-server-*-x86_64-unknown-linux-musl/oleafly-server /usr/local/bin/oleafly-server
oleafly-server --version
```

With the GitHub CLI installed, `gh attestation verify oleafly-server-*-x86_64-unknown-linux-musl.tar.gz --repo Oleafly/Oleafly` also checks that GitHub built the archive from the Oleafly repository. The archive includes the systemd unit, `oleafly-server.service`.

To build the program yourself instead, run this from a clone of the Oleafly repository on any computer with Docker. Use `--platform linux/arm64` for an ARM server.

```sh
docker buildx build --platform linux/amd64 --target binary --output type=local,dest=dist -f crates/oleafly-server/Dockerfile .
```

This writes `dist/oleafly-server`. Copy it to `/usr/local/bin/oleafly-server` on the server and make it executable.

### Run it with systemd

1. Create the settings file. Only root can read it:

   ```sh
   sudo install -d -m 0755 /etc/oleafly-server
   sudo install -m 0600 /dev/null /etc/oleafly-server/oleafly-server.env
   sudoedit /etc/oleafly-server/oleafly-server.env
   ```

   Put one setting per line. `OLEAFLY_BIND=127.0.0.1:8080` keeps the server off the network so only Caddy can reach it. Don't set `OLEAFLY_DATA_DIR`, because the unit sets it.

   ```ini
   OLEAFLY_PUBLIC_URL=https://collab.example.org
   OLEAFLY_BIND=127.0.0.1:8080
   OLEAFLY_BEHIND_PROXY=true
   OLEAFLY_SERVER_NAME=Smith Lab
   OLEAFLY_GITHUB_CLIENT_ID=Ov23liExampleClientId
   OLEAFLY_GITHUB_CLIENT_SECRET=example-client-secret
   ```

2. Install and start the unit from the unpacked archive, or from `deploy/server` in a clone of the repository. Then find the setup code:

   ```sh
   sudo install -m 0644 oleafly-server.service /etc/systemd/system/oleafly-server.service
   sudo systemctl daemon-reload
   sudo systemctl enable --now oleafly-server
   sudo journalctl -u oleafly-server | grep 'Setup code'
   ```

The unit runs the server as a user that systemd creates for it, with a read-only view of the rest of the system. The only place it can write is `/var/lib/oleafly-server`, where it keeps its data. On disk that folder is `/var/lib/private/oleafly-server`.

To keep the client secret out of the settings file, use a systemd credential. Save the secret in `/etc/oleafly-server/github-client-secret` with mode 600, delete the `OLEAFLY_GITHUB_CLIENT_SECRET` line from the settings file, run `sudo systemctl edit oleafly-server`, and add:

```ini
[Service]
LoadCredential=github-client-secret:/etc/oleafly-server/github-client-secret
Environment=OLEAFLY_GITHUB_CLIENT_SECRET_FILE=%d/github-client-secret
```

Then run `sudo systemctl restart oleafly-server`.

### Put HTTPS in front

Install Caddy with [its instructions for your distribution](https://caddyserver.com/docs/install). Replace `/etc/caddy/Caddyfile` with these lines and run `sudo systemctl reload caddy`. Caddy gets the certificate and passes live connections through.

```
collab.example.org {
	reverse_proxy 127.0.0.1:8080
}
```

For nginx or Apache, or a web server your university already runs, read [Behind a reverse proxy](/docs/collaboration/server-operations/#behind-a-reverse-proxy).

## Without a public address

If the machine has no public address, for example a computer in your lab behind the university firewall, Cloudflare Tunnel can make it reachable. You need a Cloudflare account with your domain on it. Cloudflare handles HTTPS for the hostname, so requests are decrypted on Cloudflare's network before they reach your machine. Count Cloudflare among the parties that can read traffic to your server.

1. Follow the [Docker Compose by hand](#docker-compose-by-hand) steps up to editing `.env`. Set `OLEAFLY_DOMAIN` to the hostname you'll use, such as `collab.example.org`. The one-command installer doesn't fit here, because it starts Caddy.
2. Start only the server. Caddy isn't needed here:

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

3. In the Cloudflare dashboard, go to **Networking › Tunnels**, select **Create Tunnel**, give it a name, and select **Create Tunnel** again. Copy the token from the install command it shows.
4. Start the tunnel next to the server. The `read` command waits for you to paste the token and doesn't show it:

   ```sh
   read -rs TUNNEL_TOKEN && export TUNNEL_TOKEN
   sudo --preserve-env=TUNNEL_TOKEN docker run -d --name oleafly-tunnel --restart unless-stopped --network oleafly-server_default -e TUNNEL_TOKEN cloudflare/cloudflared:2026.10.0 tunnel --no-autoupdate run
   ```

5. Back in the dashboard, open the tunnel. Under **Routes**, select **Add route**, then **Published application**. Under **Hostname**, enter the subdomain and pick your domain. Set **Service URL** to `http://oleafly-server:8080` and select **Add route**.
6. Check that it answers at `https://collab.example.org/healthz`, then [become the first admin](#become-the-first-admin).

On a machine without Docker, install `cloudflared` from [Cloudflare's package repository](https://pkg.cloudflare.com/), run `sudo cloudflared service install` followed by the token, and set **Service URL** to `http://127.0.0.1:8080`.

Cloudflare passes live connections through and puts the visitor's address last in `X-Forwarded-For`, so keep `OLEAFLY_BEHIND_PROXY=true`. When the tunnel leads to another proxy, such as Coolify's, and not straight to the server, also set `OLEAFLY_PROXY_HOPS=2`. Cloudflare refuses requests larger than 100 MB on its Free and Pro plans, so set `OLEAFLY_MAX_PROJECT_MB` to 100 or less, or large projects fail to upload. Don't put Cloudflare Access in front of the server, because Oleafly can't get past its login page.

When you update the server later, download the new release's files and name the service so Caddy stays off: `sudo docker compose up -d oleafly-server`.

## Become the first admin

A new server has no admin. Until someone finishes setup, the server makes a new one-time setup code each time it starts and writes it to its log.

1. Find the newest line that starts with `Setup code:` in the log:
   - One-command install: the installer prints it when it finishes. To see it again, run `cd /opt/oleafly-server && sudo docker compose logs oleafly-server | grep 'Setup code'`
   - Docker Compose by hand: `sudo docker compose logs oleafly-server | grep 'Setup code'` in the server's folder
   - Coolify: the logs of the `oleafly-server` service
   - systemd: `sudo journalctl -u oleafly-server | grep 'Setup code'`
2. In Oleafly, open **Settings → Collaboration**, select **Add server**, and enter your server's address. Oleafly sees that the server needs setup and asks for the **Setup code**.
3. Enter the code and sign in with any provider you configured. You become the server's admin, and the code stops working.

Anyone who has the code before you use it can make themselves admin, so only ever paste it into Oleafly.

You can also choose the code yourself with `OLEAFLY_SETUP_CODE`: at least 12 letters or digits, with dashes or spaces if you like. The server then never prints it. The log only says `Setup code: set by OLEAFLY_SETUP_CODE`, and you enter the value you set. Remove it from your settings once setup is done.

Next, let people in. [Sign-in and access](/docs/collaboration/server-access/#access-rules) explains how.

## Check the server

- With the one-command install, `sudo sh /opt/oleafly-server/install.sh status` checks the containers and HTTPS in one go.
- `https://collab.example.org/healthz` answers `200` while the server process runs.
- `/readyz` answers `200` only while the server can write to its database and its data folder. When it can't, for example on a full disk, it answers `503` and names what failed, `database` or `storage`.
- The Docker image checks `/readyz` by itself, so `docker ps` and Coolify show an unhealthy server instead of a running one that can't save anything.
- In Oleafly, **Test connection** under **Settings → Collaboration** checks the address, HTTPS, the version, live connections, sign-in options and the server's clock. **Connection checks** in the [admin console](/docs/collaboration/server-access/#admin-console) runs the same checks.
