Docs

Install the server

Stavo runs on your hardware, not our cloud. The server is one container plus Postgres. Anything that runs docker compose works: Synology, Unraid, TrueNAS, a Raspberry Pi 4 or 5, an old Mac mini, a VPS.

The short version

No account here, no key, no card. The server and the web app are free forever, for everyone on it.

  1. Save the compose file and a .env in a folder, then run docker compose up -d.
  2. Put HTTPS in front (Caddy or a Cloudflare Tunnel) and open your address. The first visit asks you to create the admin account.
  3. Invite your people from the dashboard.
  4. Want the native apps? Connect them. They run a 30 day trial on every device, and a license key unlocks them for good.

What you need

  • Docker with the compose plugin.
  • 2 GB of RAM is comfortable for a household. Add more if you turn on smart search on the server.
  • Disk space for your files, on the machine or on a NAS share.
  • A domain or subdomain if you want to use the apps and share links outside your home.

1. docker compose

Make a folder (say ~/stavo) and save this as docker-compose.yml. The server image works on both Intel and ARM machines.

yaml
name: stavo
services:
  server:
    image: git.ronniie.com/nulldaily/stavo:${STAVO_VERSION:-latest}
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_URL: postgres://stavo:${POSTGRES_PASSWORD}@db:5432/stavo
      MEDIA_DIR: /data/media
      STAVO_UPDATER_URL: http://watchtower:8080
      STAVO_UPDATER_TOKEN: ${WATCHTOWER_HTTP_API_TOKEN:-}
    labels:
      com.centurylinklabs.watchtower.enable: "true"
    ports:
      - "127.0.0.1:8710:8710"
    volumes:
      - media:/data/media
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: stavo
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: stavo
    volumes:
      - db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U stavo"]
      interval: 5s
      retries: 20

  # Only acts when you press "Update now" in Stavo. Never polls on its own.
  watchtower:
    image: nickfedor/watchtower:1.22.3@sha256:2f8f2a9bd21a195b3b650409532a20f6aada8ef5834f00d0d7a999dee0779a81
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      WATCHTOWER_HTTP_API_UPDATE: "true"
      WATCHTOWER_HTTP_API_PERIODIC_POLLS: "false"
      WATCHTOWER_HTTP_API_TOKEN: ${WATCHTOWER_HTTP_API_TOKEN}
      WATCHTOWER_LABEL_ENABLE: "true"
      WATCHTOWER_CLEANUP: "true"
    labels:
      com.centurylinklabs.watchtower.enable: "false"

volumes:
  db:
  media:

2. .env

Next to it, a .env file. These are the ones that matter:

.env
# Where people reach your server
PUBLIC_URL=https://drive.example.com
# openssl rand -hex 32
SECRET=
POSTGRES_PASSWORD=pick-something-long
# openssl rand -hex 32 (turns on one click updates)
WATCHTOWER_HTTP_API_TOKEN=

# Only when your files live on a NAS share (see "Store files on your NAS")
NAS_HOST=192.168.1.20
NAS_SHARE_PATH=Media/stavo
NAS_USER=stavo
NAS_PASSWORD=
shell
docker compose up -d
docker compose logs -f server

The server listens on 127.0.0.1:8710, on purpose only on the machine itself. Put HTTPS in front of it (next step). To try it out on the same machine first, just open http://localhost:8710.

3. Store files on your NAS

Docker runs on the NAS itself? Point the media volume at a folder on it instead of the named volume:

yaml
volumes:
      - /volume1/stavo:/data/media      # a folder on the NAS itself

Docker runs on another box? Mount an SMB share from the NAS as the media volume. Replace the volumes: block at the bottom with this, and create the folder on the NAS first:

yaml
volumes:
  db:
  media:
    driver: local
    driver_opts:
      type: cifs
      device: "//${NAS_HOST}/${NAS_SHARE_PATH}"
      o: "username=${NAS_USER},password=${NAS_PASSWORD},vers=3.0,uid=1000,gid=1000,file_mode=0664,dir_mode=0775,noserverino,soft"

Keep Postgres on a local volume (the db volume above), never on a network share. Databases and network shares don't get along.

4. HTTPS: Caddy or a Cloudflare Tunnel

Caddy gets you HTTPS certificates on its own. Point your DNS at the box and use:

Caddyfile
drive.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:8710
  request_body {
    max_size 2GB
  }
}

# Optional: old short links keep working
share.example.com {
  reverse_proxy 127.0.0.1:8710
}

No open ports or static IP? Use a Cloudflare Tunnel: cloudflared tunnel create stavo, route your hostname to it, and point it at the server:

yaml
# ~/.cloudflared/config.yml
tunnel: <your-tunnel-id>
credentials-file: /home/you/.cloudflared/<your-tunnel-id>.json
ingress:
  - hostname: drive.example.com
    service: http://localhost:8710
  - hostname: share.example.com   # optional, redirects to the main domain
    service: http://localhost:8710
  - service: http_status:404

Cloudflare's free plan caps a single request at 100 MB. Stavo uploads big files in chunks, so that's fine for the apps and the web app, but a single upload from ShareX or another uploader over 100 MB will fail through a tunnel.

Got an old short link domain like share.example.com? Point it at the same server and add it to LEGACY_HOSTS in .env. Stavo redirects it, so old links keep working.

5. Connect the apps

Stavo for Mac and for iPhone and iPad are coming soon. Everything below works in the browser today; this is how the apps will connect once they ship.

Install Stavo for Mac or for iPhone and iPad, type your server address (like https://drive.example.com) and approve the connection in the browser window that opens. No tokens to copy. Each person signs in with their own account on your server.

6. Add a license (optional)

The server never needs a key. To unlock the native apps after their trial, paste a Family or Community key in Dashboard → Instance → Apps license, or let people add their own Personal key in Profile → Your Stavo apps. How licensing works with your server.

7. Add an AI provider (optional)

In Dashboard → Instance → Assistant, pick a provider and paste its API key: Claude (recommended), OpenAI, Google Gemini, Ollama or any OpenAI-compatible server. That becomes the default for everyone; people can add their own in Profile → Assistant. More about the Assistant.

Updating

Your server checks for new versions every few hours and shows the release notes in Dashboard → Instance → Updates. With WATCHTOWER_HTTP_API_TOKEN set, Update now backs up the database, pulls the new image and restarts the server. The Watchtower container only acts when you press that button and only touches the Stavo server container.

Prefer doing it by hand? Take a backup, then:

shell
docker compose pull
docker compose up -d

Want to stay on a version? Set STAVO_VERSION=1.0.0 in .env. Every release is on the changelog.

Backups

Two things hold your data: the media folder (your files) and the Postgres database (everything about them).

  • Files: back up the media folder or NAS share with whatever you already use (Hyper Backup, restic, Time Machine, a USB disk). It's plain files in folders.
  • Database: one click updates already save a dump to .backups/ in your media folder. For regular backups, run this on a schedule:
shell
# Database (small, do this often)
docker compose exec -T db pg_dump -U stavo stavo | gzip > stavo-db-$(date +%F).sql.gz

# Restore into a fresh database
gunzip -c stavo-db-2026-11-01.sql.gz | docker compose exec -T db psql -U stavo stavo

Each person can also download everything they own from Profile → Your data, in open formats.

Optional extras

  • Smart search on the server: an extra container that indexes every photo for search and people. Skip it on low power boxes; the iPhone app indexes on the phone instead.
  • Code terminal: a sandboxed container for the terminal in Code and the Assistant's commands. No Docker socket, no network by default. Off until you turn it on.

Both are in the server's full compose file at git.ronniie.com/nulldaily/stavo, with the steps in its README.