Frame docs · Install

Install with Docker Compose

If you already use Docker, or don't want to touch systemd. The official image ghcr.io/crow-tech-pte-ltd/frame-server:0 is built for amd64 and arm64, so it also runs on a Raspberry Pi with a 64-bit OS. The :0 tag follows 0.x releases.

Get the files

Install Docker Engine with the Compose plugin, from your distribution's packages or Docker's official instructions. Then get the repository and create the config:

Terminal
git clone https://github.com/Crow-Tech-Pte-Ltd/frame.gitcd frame/deploycp frame.conf.example frame.confchmod 600 frame.conf

Every docker compose command below runs in frame/deploy. In every setup, set FRAME_PUBLIC_URL=https://frame.example.com (your address) and FRAME_NAME in frame.conf; the Caddy file sets FRAME_PUBLIC_URL for you. Then pick one way to make the server reachable.

With Caddy (automatic HTTPS)

Caddy sits in front of Frame and gets HTTPS certificates for your domain. Use this on a server with a public IP address.

  1. Step 1: Point your domain at the server

    At your DNS provider, add an A record (and optionally AAAA) for frame with the server's IP address. On Cloudflare DNS, set it to DNS only. Open ports 80 and 443 (TCP), plus 443/UDP for HTTP/3.

  2. Step 2: Write the config

    Terminal
    nano frame.conf          # set FRAME_NAME; leave FRAME_DOMAIN unsetecho "FRAME_HOSTNAME=frame.example.com" > .env

    docker-compose.caddy.yml sets two things for you from FRAME_HOSTNAME: FRAME_PUBLIC_URL=https://<FRAME_HOSTNAME>, and FRAME_TRUSTED_PROXIES set to the Compose network Caddy lives on (172.31.250.0/24, pinned in the file), so Frame sees each visitor's IP instead of Caddy's.

  3. Step 3: Start it

    Terminal
    docker compose -f docker-compose.caddy.yml up -d     # pulls the official imagedocker compose -f docker-compose.caddy.yml logs -f frame

    Open https://frame.example.com/healthz. It should say ok. Caddy health-checks Frame through the same path. Then create your admin account.

With Cloudflare Tunnel

Run Frame at home with no open ports and no router changes. This also works behind carrier-grade NAT. You need a domain whose DNS is on Cloudflare (the free plan is fine).

  1. Step 1: Create the tunnel

    In the Cloudflare dashboard, open Zero Trust → Networks → Tunnels → Create a tunnel, choose Cloudflared and name it frame. Copy the tunnel token: it is a secret. Add a public hostname frame.example.com whose service is http://frame:8080.

  2. Step 2: Write the config

    Terminal
    nano frame.conf          # FRAME_PUBLIC_URL=https://frame.example.com and FRAME_NAMEecho "CLOUDFLARE_TUNNEL_TOKEN=<your tunnel token>" > .env
  3. Step 3: Switch docker-compose.yml to the tunnel

    In docker-compose.yml:

    • remove the ports: block of the frame service;
    • uncomment the cloudflared service;
    • uncomment the line marked (b), FRAME_TRUSTED_PROXIES: "cloudflare,172.31.251.0/24". Frame then trusts the visitor IP only when it comes from the tunnel container.
  4. Step 4: Start it

    Terminal
    docker compose up -d                 # pulls the official imagedocker compose logs -f frame

    On your phone, on mobile data, open https://frame.example.com/healthz. It should say ok.

With built-in HTTPS

Frame can get its own Let's Encrypt certificate. Set FRAME_DOMAIN=frame.example.com and FRAME_PUBLIC_URL=https://frame.example.com in frame.conf, and in docker-compose.yml replace the 127.0.0.1:8080:8080 port line with 80:80 and 443:443. DNS and firewall are as in the Caddy setup above. Leave FRAME_TRUSTED_PROXIES unset: phones connect to Frame directly. Then docker compose up -d.

Behind your own proxy on the host

If nginx, Caddy or another proxy already runs on the host and forwards to 127.0.0.1:8080: keep the 127.0.0.1:8080:8080 port line and uncomment the line marked (d), FRAME_TRUSTED_PROXIES: "172.31.251.1/32". Docker delivers those connections from the network's gateway address. Your proxy needs the settings in Behind Caddy, nginx or another proxy.

The compose file pins its Docker network to 172.31.251.0/24 so these lines can name it. If that collides with a network you already use, change it in the file and in the lines above.

Admin commands in Docker

Find the one-time admin setup link, and run any admin command, through the container (add -f docker-compose.caddy.yml after docker compose for the Caddy setup):

Terminal
docker compose logs frame | grep "admin account"docker compose exec frame frame-server admin setup-link     # a fresh link, if neededdocker compose exec frame frame-server admin user listdocker compose exec frame frame-server admin user create --email [email protected] --name "You"

Every command is in the configuration reference.

Build the image yourself

Both setups pull the official image. To build it from the repository instead (from deploy/Dockerfile), add the docker-compose.build.yml override:

Terminal
# Cloudflare Tunnel, built-in HTTPS or host proxy (docker-compose.yml)echo "COMPOSE_FILE=docker-compose.yml:docker-compose.build.yml" >> .envdocker compose up -d --build # Caddydocker compose -f docker-compose.caddy.yml -f docker-compose.build.yml up -d --build

With the COMPOSE_FILE line in deploy/.env, later docker compose commands use your own build too. With the Caddy file, keep passing both -f options.

What the container does

The image is distroless: no shell and no package manager. The container runs as an unprivileged user (uid 65532) on a read-only filesystem, with no Linux capabilities and no-new-privileges. Everything Frame stores (database, local media and secrets.json) lives in the Docker volume frame_frame-data: back it up (Backups & restore). There is no Docker health check, because the image has no shell or curl: monitor https://frame.example.com/healthz from outside, or use the Caddy setup, which checks it for you.

For AI tools: this page as Markdown, the page index and all pages in one file.

WhatsApp