Frame docs · Get started
Set up with an AI agent
If you use a coding agent in a terminal, it can do the setup with you. Copy for my AI, at the top of every docs page, copies the prompt below. Paste it into Claude Code, Codex, Gemini CLI or another agent that runs on your server, or on a computer that can reach it over SSH.
What the agent does
- Asks you first: where the server runs, your domain, Docker or the Linux installer, how private the admin panel should be, who can join, and where backups go.
- Installs Frame the way these docs describe, reading the installer before it runs it, and checks HTTPS with
/healthz. - Hands the one-time admin setup link to you: you create the admin account and turn on two-factor authentication yourself.
- Optionally makes the admin panel private, sets up e-mail resets or R2, and schedules nightly backups.
- Checks each step, and knows the common pitfalls from Troubleshooting.
The prompt
Setup prompt for Frame
You are helping me self-host a Frame server: the open-source server (AGPL-3.0, https://github.com/Crow-Tech-Pte-Ltd/frame) behind the Frame photo-sharing apps for iPhone and Android. Goal: `frame-server` running on my Linux machine over HTTPS at my own domain, my admin account created with two-factor authentication, the admin panel only as exposed as I want, the apps connected, and nightly backups that leave the machine. Work one step at a time and tell me what you are about to run before you run it. If what you see doesn't match these instructions, read the matching docs page (links at the end; add `.md` for Markdown) before you improvise. Don't invent settings: every `FRAME_*` setting is in the configuration reference. ## Before you start, ask me 1. Where will it run: a VPS with a public IP, a Raspberry Pi or home computer (no open ports), or a server that already runs Docker or a web server? Are you running on that machine, or do you reach it over SSH (which host and user)?2. The domain or subdomain people will type into the app (for example `frame.example.com`), and who manages its DNS (Cloudflare or another provider).3. Which setup: the Linux installer with systemd (built-in HTTPS, Cloudflare Tunnel, or behind an existing Caddy/nginx), or Docker Compose (with Caddy, a Cloudflare Tunnel, built-in HTTPS, or behind an existing proxy)?4. An e-mail address for Let's Encrypt (built-in HTTPS only) and the server name to show in the app.5. Should the admin panel stay on the internet at `/admin`, be reachable only over an SSH tunnel, or be off? Do I want Server admin in the apps anyway?6. Who can join: invite-only (default), closed, or open? Do I want e-mail password resets (SMTP server details), and media on local disk or in Cloudflare R2?7. Where should backups be copied to (my computer with `scp`, or a `restic`/`rclone` destination)? ## Prerequisites - Linux with systemd on amd64, arm64 or armv7 (installer), or Docker Engine with Compose on amd64 or arm64 (a 64-bit OS on a Pi). 1 CPU core and 512 MB–1 GB of RAM are enough.- A domain I can add DNS records to. The apps need HTTPS; they reject self-signed certificates.- Built-in HTTPS or Caddy: ports 80 and 443 reachable from the internet (plus 22 for SSH). Cloudflare Tunnel: the domain's DNS on Cloudflare, and no open ports needed. ## Steps 1. **DNS.** Built-in HTTPS or Caddy: an `A` record (and optionally `AAAA`) for the hostname pointing at the server, **DNS only** if it's on Cloudflare. Check with `dig +short frame.example.com`. Tunnel: I create the tunnel in the Cloudflare dashboard (Zero Trust → Networks → Tunnels) and keep the token secret.2. **Firewall** (not for a tunnel): `sudo ufw allow OpenSSH`, `sudo ufw allow 80,443/tcp`, `sudo ufw enable`, and the provider's firewall too.3. **Install.** Pick the matching path: - Linux installer: download, show me, then run it. Never pipe it into a shell. ```sh curl -fsSLO https://github.com/Crow-Tech-Pte-Ltd/frame/releases/latest/download/install.sh less install.sh sudo sh install.sh ``` Then edit `/etc/frame/frame.conf` (every line starts commented out). Built-in HTTPS: `FRAME_DOMAIN`, `FRAME_PUBLIC_URL=https://<domain>`, `FRAME_ACME_EMAIL`, `FRAME_NAME`, then `sudo sh install.sh --reconfigure` and `sudo systemctl start frame`. Tunnel: `FRAME_LISTEN=127.0.0.1:8080`, `FRAME_PUBLIC_URL`, `FRAME_TRUSTED_PROXIES=cloudflare,127.0.0.1/32`, `FRAME_NAME` and no `FRAME_DOMAIN`; then install `cloudflared` with the two commands Cloudflare shows me and set the public hostname's service to HTTP `localhost:8080`. Existing Caddy/nginx: `FRAME_LISTEN=127.0.0.1:8080`, `FRAME_PUBLIC_URL`, `FRAME_TRUSTED_PROXIES=127.0.0.1/32,::1/128`, and a proxy that passes `Host` and `X-Forwarded-For`, allows 64 MB bodies and doesn't buffer `/api/v1/events`. - Docker Compose: `git clone https://github.com/Crow-Tech-Pte-Ltd/frame.git`, `cd frame/deploy`, `cp frame.conf.example frame.conf`, `chmod 600 frame.conf`. With Caddy: leave `FRAME_DOMAIN` unset, `echo "FRAME_HOSTNAME=<domain>" > .env`, then `docker compose -f docker-compose.caddy.yml up -d` (pass `-f docker-compose.caddy.yml` to every later command). With a tunnel: set `FRAME_PUBLIC_URL` and `FRAME_NAME`, put `CLOUDFLARE_TUNNEL_TOKEN=…` in `.env`, and in `docker-compose.yml` remove the `ports:` block, uncomment the `cloudflared` service and the (b) `FRAME_TRUSTED_PROXIES` line; the tunnel's service is `http://frame:8080`. Then `docker compose up -d`.4. **Check it:** `https://<domain>/healthz` says `ok` (from a phone on mobile data for a tunnel), and `https://<domain>/api/v1/server` shows JSON with the server name. Logs: `journalctl -u frame -n 100` or `docker compose logs frame`.5. **Admin account.** The server logs a one-time setup link (valid once, for 24 hours). Let me run the command that shows it myself, so the link stays out of this chat: `sudo journalctl -u frame | grep "admin account"` or `docker compose logs frame | grep "admin account"` (a fresh link: `sudo frame-admin setup-link` or `docker compose exec frame frame-server admin setup-link`). Then stop: I open it, create the account, and turn on two-factor authentication under **Security** in the panel. Admin commands always go through `sudo frame-admin …` (never `frame-server` as root) or `docker compose exec frame frame-server admin …`.6. **Admin panel exposure** (only if I asked): `FRAME_ADMIN_LISTEN=127.0.0.1:8081` and I reach it with `ssh -N -L 8081:127.0.0.1:8081 me@server` and `http://localhost:8081/admin/`; or `FRAME_ADMIN=local` (a proxy or tunnel on the same host must then be in `FRAME_TRUSTED_PROXIES`); or `FRAME_ADMIN=off`. Add `FRAME_ADMIN_APP=on` if I still want Server admin in the apps. Keep `FRAME_PUBLIC_URL` set. With Docker, publish `127.0.0.1:8081:8081` and use `FRAME_ADMIN_LISTEN=:8081`. Restart after every change.7. **Optional settings:** `FRAME_REGISTRATION` (`invite` is already the default; setting it locks it in the panel), `FRAME_SMTP_URL` and `FRAME_SMTP_FROM` (quote secrets containing `$` or `\` in single quotes), R2 with `FRAME_STORAGE=s3` and the `FRAME_S3_*` settings (a private bucket and a token scoped to it; decide before people post).8. **Connect the apps.** I install Frame, type the domain without `https://` at **Where's your Frame?**, and sign in with the admin account. Friends join with an invite from the panel (**Invites → New invite**) or from the app.9. **Push** works out of the box through Crow Tech's content-blind relay at `https://frame.crow.sg` (end-to-end encrypted; it stores nothing). Only if I want it off: `FRAME_PUSH_RELAY=` (empty). Check the server can reach it: `curl -sI https://frame.crow.sg/healthz`.10. **Backups**, nightly, copied off the machine. systemd: `sudo frame-admin backup /root/frame-$(date +%F).db` plus `sudo tar -C /var/lib/frame -czf /root/frame-files-$(date +%F).tgz secrets.json media`, in root's crontab (escape `%` as `\%` there). Docker: `docker compose exec frame frame-server backup /data/frame-backup.db`, `docker compose cp frame:/data/frame-backup.db ./frame-$(date +%F).db`, and a tar of `secrets.json` and `media` from the `frame_frame-data` volume. Then help me test a restore. ## Verify at the end - `systemctl status frame` says active (or `docker compose ps` shows it up) and `/healthz` says `ok` over HTTPS with a valid certificate.- The admin panel opens where I chose, and is `404` at `https://<domain>/admin` if I made it private.- I'm signed in in the app, two-factor authentication is on, and an invite link works.- A backup file and the files archive exist off the machine. ## Common pitfalls - Certificates fail: the DNS record is proxied (orange cloud), ports 80/443 aren't reachable, or `install.sh --reconfigure` wasn't run after setting `FRAME_DOMAIN` (log says "permission denied"). Let's Encrypt rate-limits retries: wait an hour.- Apple/Google sign-in buttons missing: `FRAME_PUBLIC_URL` not set to the exact address people type.- Everyone gets "too many requests": `FRAME_TRUSTED_PROXIES` doesn't match the proxy or tunnel. Unset it with built-in HTTPS.- Cloudflare Access in front of the hostname blocks the apps: don't add it.- Uploads fail behind a proxy: allow about 64 MB request bodies. Slow live updates: turn off buffering for `/api/v1/events`.- "readonly database" after admin commands: someone ran `frame-server` as root. Fix with `sudo chown -R --reference=/var/lib/private/frame /var/lib/private/frame && sudo systemctl restart frame`. ## Rules for you - Never pipe a downloaded script into a shell; download it, show me, run it.- Treat the setup link, tunnel token, SMTP and R2 credentials, `frame.conf` and `secrets.json` as secrets: don't print them into this chat or logs, and never commit them. Prefer that I paste secrets into files myself.- Stop and let me act when a browser, DNS dashboard, Cloudflare dashboard or the phone is involved.- Don't open other ports, put a login page in front of the server, delete `/var/lib/frame` or the Docker volume, or switch storage after people have posted.- `secrets.json` must be backed up: losing it makes R2 media unreadable forever. ## Full documentation - Overview: https://crow.sg/apps/frame/docs/ (Markdown: https://crow.sg/apps/frame/docs/index.md)- Quickstart: https://crow.sg/apps/frame/docs/quickstart/- Docker Compose: https://crow.sg/apps/frame/docs/install-docker/- Linux installer: https://crow.sg/apps/frame/docs/install-systemd/- First run & admin account: https://crow.sg/apps/frame/docs/first-run/- Connect the apps & invite: https://crow.sg/apps/frame/docs/connect/- Keep the admin panel private: https://crow.sg/apps/frame/docs/private-admin/- Push notifications: https://crow.sg/apps/frame/docs/push/- Storage & sizing: https://crow.sg/apps/frame/docs/storage/- Backups & restore: https://crow.sg/apps/frame/docs/backups/- Configuration: https://crow.sg/apps/frame/docs/configuration/- Troubleshooting: https://crow.sg/apps/frame/docs/troubleshooting/- Every page in one Markdown file: https://crow.sg/apps/frame/docs/llms-full.txtThe agent can read any page of these docs as Markdown: add .md to a page's address (the overview is index.md), or fetch llms-full.txt for everything in one file.
For AI tools: this page as Markdown, the page index and all pages in one file.