# Potato Server Requirements for Your IT Team

Source: https://www.potatoannotator.com/docs/deployment/it-handoff

**Potato is a self-hosted web application for annotation studies. One Potato process serves one study, keeps every annotation in files and SQLite databases inside the study's folder, and needs a reverse proxy in front of the process for HTTPS. A default install sends nothing to outside services, and data leaves the server only through features the study's configuration turns on.**

This page is written for the people who run servers at a university, hospital, or company. A researcher who wants a study hosted can send the page unchanged, together with the project folder and the answers listed in the next section. [Put Your Potato Study Online](/docs/deployment/put-your-study-online) covers the routes a researcher can take without an IT team.

## Information from the research team

The research team sends the project folder, which the [Study Designer](/designer) exports as **Download project (.zip)**. The folder holds `config.yaml`, the study's configuration, and the data files the configuration names. Alongside the folder, the research team answers these questions:

- How many annotators take part, and do annotators sign in with institutional accounts or with usernames the researcher lists?
- When does the study start and end?
- Do the items or the answers contain personal or sensitive information, and under which ethics approval does the study run?
- Does the configuration use AI features that send items to a model provider? Blocks such as `ai_support`, `chat_support`, and `solo_mode` in `config.yaml` mark such features.

## Software

Potato is a Python web application built on Flask. The simplest install is the published Docker image, `ghcr.io/davidjurgens/potato`, built for `linux/amd64` and `linux/arm64`. The image is about 900 MB, runs Python 3.11 under gunicorn as the unprivileged user `potato` (uid 1000), and listens on port 7860. The project folder mounts at `/app`:

```bash
docker run -d --name potato-study --restart unless-stopped \
  -p 127.0.0.1:8000:7860 \
  -v /srv/potato/my-study:/app \
  -e POTATO_SECRET_KEY="$(openssl rand -hex 32)" \
  ghcr.io/davidjurgens/potato:2.14.0
```

The command publishes the study only on port 8000 of the host's loopback interface. The reverse proxy is then the single way in. The `latest` tag follows Potato's development branch, and a study should pin a version tag. Generate `POTATO_SECRET_KEY` once and keep the value, because a new key on every start logs every annotator out at each restart. The container refuses to start when `/app` is not writable by uid 1000. Run the container with `--user "$(id -u):$(id -g)"` or give the folder to uid 1000. [Run Potato in Docker](/docs/deployment/docker) covers the image's tags and environment variables.

Without Docker, Potato installs from PyPI on Python 3.9 or newer with `pip install potato-annotation`. Add the `hosting` extra for backups and single sign-on, and the `saml` extra for SAML sign-in, which builds against the system's libxmlsec1. `potato start config.yaml` runs a development server that closes the connection after every response. For annotators working over the internet, run Potato under gunicorn as [Production Setup](/docs/deployment/production-setup) shows, or put a reverse proxy in front of `potato start`.

## Process and worker count

Potato keeps the item pool, the assignment queue, and every annotator's state in the memory of one process. A second gunicorn worker or a second replica holds a separate copy of all three, hands out items the first process already assigned, and overwrites the first process's saves. The image therefore refuses to start with `GUNICORN_WORKERS` above 1. For more concurrency, raise `GUNICORN_THREADS` from the default of 8. Potato's own Docker documentation puts eight threads at the dozens of simultaneous annotators a typical study has. Run several studies as several containers, each container with a separate folder and port.

## Server size

Memory grows with the number of items. Potato's measurements put each item at 0.9 to 1.4 KB in memory, and the server on Potato's reference dataset used about 365 MB. `potato deploy`, Potato's own cloud command, picks a machine with 1 or 2 vCPUs and 2 GB of memory on most hosts, which is a reasonable starting size.

The disk has to be persistent and writable, and the disk needs room for the data files, any media files the items include, and the annotations. A container whose disk is wiped on restart loses every annotation unless the built-in backup described below is on.

## Network access and HTTPS

Expose Potato through a reverse proxy such as nginx, Apache, or Caddy. The proxy holds the TLS certificate and forwards requests to the container's port. Only ports 443, and 80 for redirects and certificate renewal, need to be open to the internet. [Reverse Proxy](/docs/deployment/reverse-proxy) shows how to serve Potato under a URL path such as `https://host.example.edu/my-study/`, through `POTATO_PROXY_FIX=1` with an `X-Forwarded-Prefix` header or through `POTATO_URL_PREFIX`. The live agent viewers stream with server-sent events. Turn off proxy buffering on the `/api/` location if the study uses those viewers.

`GET /health` needs no authentication and returns `{"status": "ok"}` once the server is ready, and 503 while the server is still loading data. Point a load balancer or monitoring check at that route.

Potato needs no outbound network access by default. The scripts, stylesheets, and fonts that annotators' browsers load come from the server itself, and [Air-Gapped and Offline Deployment](/docs/measurement/air-gap) lists how Potato keeps the default install offline. Outbound access is needed only for features the configuration turns on:

- AI features call the model endpoint the configuration names, unless the endpoint is a local model server such as Ollama.
- Backups write to the Hugging Face dataset or S3 bucket the configuration names.
- Single sign-on contacts the identity provider.
- Remote data sources read from the bucket or database the configuration names.
- Items that link to media by URL make the annotator's browser fetch the media from that URL.

## Files Potato writes

Potato writes everything into the study's folder:

| Path in the project folder | Contents |
|---|---|
| `<output_annotation_dir>/<username>/user_state.json` | each annotator's answers, rewritten in full on every save |
| `user_config.json`, next to the output folder | the account list, with salted PBKDF2-SHA256 password hashes |
| `project.sqlite`, `datasets.sqlite` | project data such as memos, codebooks, and review state, in SQLite's WAL mode |
| `admin_api_key.txt` | the generated admin key, readable only by the server's user, when no key is set |
| `potato.log` | the server log |

One directory therefore holds the whole study. `output_annotation_dir` is a setting in `config.yaml`, and the Study Designer sets the value to `output`.

## Backups

A nightly backup of the project folder covers the annotation files and the account list. Copy the two SQLite files with SQLite's own backup command rather than as files, because a file copy of a database in WAL mode can be corrupt or miss recent writes:

```bash
sqlite3 /srv/potato/my-study/project.sqlite ".backup /backups/my-study/project.sqlite"
```

Potato's built-in backup copies the annotation output and snapshots of the databases to an S3-compatible bucket or a private Hugging Face dataset every five minutes. On start, the backup restores from the bucket when the disk is empty, and the backup never overwrites annotations already on disk. The block below sends the copy to a university object store. Add the block to the study's `config.yaml`, and set `POTATO_S3_ACCESS_KEY_ID` and `POTATO_S3_SECRET_ACCESS_KEY` in the container's environment:

```yaml
backup:
  schedule_minutes: 5
  restore_on_boot: true
  sinks:
    - type: s3
      bucket: research-backups
      prefix: potato/my-study
      endpoint_url: https://s3.example.edu
```

The published image includes the backup clients. A pip install needs the `hosting` extra.

## Sign-in options

Potato supports several ways for annotators to sign in, set in `config.yaml`:

| Method | Suits | What the server needs |
|---|---|---|
| Named accounts with passwords (the default) | a known list of annotators | `user_config.users` lists the usernames, with `allow_all_users: false`. Potato refuses registration for anyone not listed |
| Username only (`require_password: false`) | low-stakes tasks and classroom exercises | nothing more. The username list still applies |
| OAuth and OpenID Connect | Google, GitHub, or Hugging Face accounts, or any OpenID Connect provider such as Microsoft Entra ID or Okta | a client ID and secret, and the callback URL `https://<host>/auth/callback/<provider>` registered with the provider |
| SAML 2.0 | Shibboleth, ADFS, and SAML apps in Okta and Microsoft Entra | the `saml` extra, or the image built with `--build-arg POTATO_EXTRAS=saml`. Potato serves the service-provider metadata at `/saml/metadata` and receives assertions at `/saml/acs` |
| Links from a crowdsourcing platform | Prolific, SONA, CloudResearch Connect, and similar platforms | the platform's study URL pointed at the server |

Passwords are hashed with PBKDF2-SHA256 at 100,000 iterations and a per-user salt. A forgotten password is reset through a single-use link that Potato shows on screen, and Potato sends no email. Behind a proxy that ends TLS, set `sp_base_url` for SAML to the public address, or the identity provider's response fails Potato's destination check. [SSO and OAuth](/docs/deployment/sso-oauth), [Password Management](/docs/deployment/password-management), and [Crowdsourcing Integration](/docs/deployment/crowdsourcing) give the configuration for each method.

## Admin access

The [admin dashboard](/docs/features/admin-dashboard) at `/admin` shows progress, agreement, and every annotator's answers, and downloads the results. The dashboard opens with an admin key. Set the key through `POTATO_ADMIN_API_KEY`, or let Potato generate `admin_api_key.txt` in the project folder on first use, and pass the key to the researcher through a channel your institution trusts. Keep `debug: false` in `config.yaml` on any server, because debug mode opens the admin pages without a key to requests that look local, and a proxy on the same machine can make every request look local.

## Data protection

Potato is self-hosted, so the annotations, the account list, and the items stay on the server you run. Potato contains no analytics or usage reporting, and a default install makes no outbound connections. Data leaves the server only through the features listed under [Network access and HTTPS](#network-access-and-https), each of which the study's configuration has to turn on and name a destination for.

Check three points against the study's ethics approval:

- The annotation files name each annotator by username, so usernames that are email addresses put email addresses in the data.
- Survey pages in a study can collect personal information, and the answers sit in the same per-annotator files.
- When the configuration sends items to a hosted model provider, the items leave the server under that provider's terms.

When the study ends, the research team downloads the results from the admin dashboard. Agree with the team how long the server copy is kept, and delete the project folder and any backup bucket after that date.

## Further reading

- [Run Potato in Docker](/docs/deployment/docker), for the image's tags, environment variables, and file ownership
- [Production Setup](/docs/deployment/production-setup), for gunicorn, nginx, Apache, and Docker Compose
- [Reverse Proxy](/docs/deployment/reverse-proxy), for serving Potato under a URL path
- [Air-Gapped and Offline Deployment](/docs/measurement/air-gap), for networks with no outbound access
- [Put Your Potato Study Online](/docs/deployment/put-your-study-online), the researcher's side of the same process
