# Configuration reference

> Every Frame server setting (FRAME_PUBLIC_URL, FRAME_REGISTRATION, FRAME_ADMIN, FRAME_PUSH_RELAY, storage, limits, e-mail and more) and every admin command.

Source: https://crow.sg/apps/frame/docs/configuration/

Settings come from environment variables, optionally loaded from a `KEY=value` file. With **no configuration at all**, `frame-server` runs a working server on plain HTTP port `8080` with its data in `./frame-data`.

## The config file

| Install | File | Restart after a change |
|---|---|---|
| Linux installer | `/etc/frame/frame.conf` (root-only, mode 0600) | `sudo systemctl restart frame` |
| Docker Compose | `deploy/frame.conf`, next to `docker-compose.yml` | `docker compose up -d` |
| Binary | any file passed with `--config` | restart the process |

- One `KEY=value` per line; lines starting with `#` are comments. Real environment variables override the file.
- Wrap values containing spaces, quotes or `#` in double quotes (`FRAME_NAME="Daniel's Frame"`). Wrap secrets containing `$` or `\` in single quotes.
- The server name, who can join, members' invites and the limits can also be changed in the admin panel, unless they are set in the file: anything set in the file shows as locked ("set in frame.conf") in the panel. Leave them out to manage them from the panel.
- Keep the file private and never commit it: it can contain passwords and keys. The annotated example is [`deploy/frame.conf.example`](https://github.com/Crow-Tech-Pte-Ltd/frame/blob/main/deploy/frame.conf.example).

In Docker, the compose files fix `FRAME_DATA_DIR` and `FRAME_LISTEN` inside the container, and the Caddy file also clears `FRAME_DOMAIN` and sets `FRAME_PUBLIC_URL` and `FRAME_TRUSTED_PROXIES`, overriding `frame.conf`.

## Settings

### Basics and sign-in

| Setting | Default | What it does |
|---|---|---|
| `FRAME_PUBLIC_URL` | `https://$FRAME_DOMAIN` | The address(es) people type into the app, comma-separated if the server is reachable under several names (the first is used in invite links). **Required for Sign in with Apple and Google**: without it, those methods are turned off. |
| `FRAME_NAME` | `Frame` | Server name shown in the app. Panel setting. |
| `FRAME_REGISTRATION` | `invite` | `invite` (new accounts need an invite), `open` (anyone who knows the address) or `closed` (only admins create accounts). Panel setting. |
| `FRAME_USER_INVITES` | `true` | On invite-only servers, whether members' invite links from the app can also bring in new people. Panel setting. |
| `FRAME_AUTH_METHODS` | `email,phone,apple,google` | Sign-in methods offered. Phone numbers are just identifiers: Frame sends no SMS. |
| `FRAME_APPLE_AUDIENCES` | `com.crowtech.frame,com.crowtech.frame.signin` | Accepted audiences for Sign in with Apple. Change only if you build and sign the apps yourself. |
| `FRAME_GOOGLE_CLIENT_IDS` | the official Frame apps' client ids | Accepted audiences for Google sign-in. Change only if you build the apps yourself. |

### Network and HTTPS

| Setting | Default | What it does |
|---|---|---|
| `FRAME_DOMAIN` | — | Serve HTTPS on `:443` with automatic Let's Encrypt certificates and redirect `:80` to HTTPS. With the Linux installer, run `sudo sh install.sh --reconfigure` after setting it. |
| `FRAME_ACME_EMAIL` | — | Optional contact e-mail for Let's Encrypt. |
| `FRAME_LISTEN` | `:8080` | Plain-HTTP listen address, for use behind a reverse proxy or tunnel. `127.0.0.1:8080` keeps it reachable from this machine only. Ignored when `FRAME_DOMAIN` is set. |
| `FRAME_TRUSTED_PROXIES` | — | Comma-separated IPs or CIDRs of reverse proxies whose `X-Forwarded-For` is trusted, so rate limits see real visitors. The word `cloudflare` also trusts `CF-Connecting-IP`, but only from loopback and the listed CIDRs. Leave unset when phones connect directly. |

### Admin panel

| Setting | Default | What it does |
|---|---|---|
| `FRAME_ADMIN` | `on` | Web admin panel at `/admin`: `on`, `local` (only from loopback and `FRAME_TRUSTED_PROXIES` networks) or `off`. |
| `FRAME_ADMIN_LISTEN` | — | A separate listen address for the panel, such as `127.0.0.1:8081`; `/admin` is then not served on the main address. |
| `FRAME_ADMIN_APP` | `auto` | Server admin in the apps: `auto` (only when the panel is on the main address), `on` (also with a private panel; `FRAME_ADMIN=off` still wins) or `off`. |

See [Keep the admin panel private](https://crow.sg/apps/frame/docs/private-admin/).

### Storage

| Setting | Default | What it does |
|---|---|---|
| `FRAME_DATA_DIR` | `./frame-data` | The database (`frame.db`), local media (`media/`) and generated secrets (`secrets.json`). The installer and Docker set it (`/var/lib/frame`, `/data`). Back it up. |
| `FRAME_STORAGE` | `local` | `local` or `s3`. |
| `FRAME_S3_ENDPOINT` | — | For example `https://<account-id>.r2.cloudflarestorage.com`. |
| `FRAME_S3_BUCKET` | — | Bucket name (a private bucket). |
| `FRAME_S3_REGION` | `auto` | `auto` for R2. |
| `FRAME_S3_ACCESS_KEY_ID`, `FRAME_S3_SECRET_ACCESS_KEY` | — | Credentials of a token scoped to the bucket. |
| `FRAME_S3_PREFIX` | `frame/` | Object key prefix. |
| `FRAME_S3_ENCRYPT` | `true` | Encrypt objects (AES-256-GCM) before upload. The key is in `secrets.json`: losing it loses the media. |

See [Storage & sizing](https://crow.sg/apps/frame/docs/storage/).

### Push and invites

| Setting | Default | What it does |
|---|---|---|
| `FRAME_PUSH_RELAY` | `https://frame.crow.sg` | Push relay base URL. An empty value turns push off. |
| `FRAME_INVITE_BASE` | `https://frame.crow.sg/i` | Base of shareable invite links. The server address travels in the link's `#` fragment, which browsers never send to that page. |

### Limits

| Setting | Default | What it does |
|---|---|---|
| `FRAME_MAX_UPLOAD_MB` | `50` | Maximum upload size per photo or video. Panel setting. |
| `FRAME_MAX_VIDEO_SECONDS` | `15` | Maximum video length the apps record and accept. Panel setting. |
| `FRAME_MAX_FRIENDS` | `0` | Maximum friends per user; `0` is unlimited. Panel setting. |

### E-mail and logging

| Setting | Default | What it does |
|---|---|---|
| `FRAME_SMTP_URL` | — | For example `smtps://user:password@smtp.example.com:465`. Enables "forgot password" e-mails; without it, admins reset passwords in the panel. |
| `FRAME_SMTP_FROM` | — | From address for reset e-mails. |
| `FRAME_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error`. Logs are JSON on stderr and never contain passwords, tokens or message text. |

For e-mail password resets, for example:

```ini title="frame.conf"
FRAME_SMTP_URL='smtps://user:password@smtp.example.com:465'
FRAME_SMTP_FROM=frame@example.com
```

## Commands

With the Linux installer, run admin commands as `sudo frame-admin …` (for example `sudo frame-admin user list`; `frame-admin` runs `frame-server admin …` as the service's user). With Docker, use `docker compose exec frame frame-server …`.

```text title="frame-server"
frame-server [serve] [--config FILE]       run the server (default command)
frame-server dev                           dev server on :8787, ./server/.devdata, seeded demo users
frame-server admin setup-link              print a one-time link to create the first admin (also printed at first start)
frame-server admin user list
frame-server admin user create --email E | --phone P [--name N] [--username U] [--admin]   (prints a one-time password)
frame-server admin user promote <username>      make an existing user an admin of the web panel
frame-server admin user demote <username>       (refuses to remove the last admin)
frame-server admin user reset-2fa <username>    turn off an admin's two-factor authentication
frame-server admin user reset-password <username|email|phone>                     (prints a one-time password)
frame-server admin user unlink <username|email|phone> apple|google                  (removes a linked sign-in, signs out all devices)
frame-server admin user delete <username>
frame-server admin invite create <username>                                     (prints that user's friend-invite link)
frame-server admin invite server [--uses N] [--days D] [--note TEXT]           (prints a server invite link; --days 0 = never expires)
frame-server backup <file.db>                online, consistent SQLite backup
frame-server version
```

Dev mode differs on purpose: it opens registration, turns push off and seeds `alice` (password `password123`) as an admin instead of printing a setup link. See [Try it locally](https://crow.sg/apps/frame/docs/install-binary/#dev).
