Running Potato Locally or in the Cloud
Run Potato on your laptop with pip or Docker, share it for an afternoon, or put a study on AWS, Jetstream2, Hetzner, Fly or Railway with one command.
Potato runs on your own machine with two commands, and potato deploy puts the same config on a cloud host with one more. This page matches each common situation to a host and gives the command for it. The cloud targets below need Potato 2.10.0 or later, which added AWS, Heroku, Fly, Railway, Hetzner, Vultr, Linode and OpenStack to the DigitalOcean, Render and HuggingFace targets that 2.9 already had.
Choosing where to run a task
The right host depends on how long the task has to stay up and who pays for it. Costs in the table are the estimates Potato 2.10.0 prints for each target's default size, and --dry-run prints the estimate for the size you choose.
| Situation | Command | Monthly cost |
|---|---|---|
| Building or trying a task | potato start config.yaml | free |
| A pilot with a few people for an afternoon | potato share config.yaml | free |
| A study, with an AWS account | potato deploy up config.yaml --provider aws | $12 |
| A study at a US institution, with no budget | potato deploy up config.yaml --provider openstack --cloud jetstream2 | free with an ACCESS allocation |
| A study, at the lowest price | potato deploy up config.yaml --provider hetzner | about €6 |
| A study, with no server to maintain | potato deploy up config.yaml --provider fly | about $6 |
| Other people run copies in their own accounts | potato deploy button config.yaml --target heroku | set by the host |
| A server your institution already runs | the Docker image behind a reverse proxy | none from Potato |
On AWS, use Lightsail, which is what --provider aws creates. Its flat price includes the IPv4 address and a 60 GB disk, and it needs only lightsail:* permissions, so it works under a restricted IAM role. The EC2 target (aws-ec2) is for accounts where Lightsail is turned off.
Jetstream2 is an NSF-funded research cloud. With an ACCESS allocation it costs nothing, and each instance gets a DNS name, so annotators see an ordinary hostname with an ordinary certificate. The default instance uses 2 service units an hour, about 17,500 over a year, so destroy it when the study ends.
Running Potato on your own machine
Potato needs Python 3.9 or newer. Install it and start a task:
pip install potato-annotation
potato start myproject/config.yaml -p 8000The task is at http://localhost:8000, and nobody else can reach it. potato start reads the config and data files where they sit, so a restart picks up an edit, which makes it the command to use while you write a task. The Quick Start builds a first config, and Installation covers the optional extras and virtual environments.
The published Docker image needs no Python on the host. It holds Potato and its dependencies, and your project folder, with config.yaml and its data, mounts at /app:
docker run -p 8000:7860 -v "$PWD/myproject:/app" ghcr.io/davidjurgens/potato:latestThe container serves on port 7860, which the command maps to 8000 on your machine. Production Setup covers the image's tags, its environment variables, and the file-ownership error that Linux hosts report.
A temporary public link with potato share
potato share runs the task and puts it on a public HTTPS link for as long as the command runs. It needs a tunnel client, and uses cloudflared, Tailscale or ngrok, in that order, whichever is installed:
brew install cloudflared
potato share myproject/config.yamlThe link stops working when you press Ctrl-C, when the laptop sleeps, or when the network changes, and the annotations stay on your own disk. Before it opens the tunnel, potato share prints who will be able to sign in and asks you to confirm, because the config's sign-in rules then apply to anyone with the link. Some university networks block trycloudflare.com links, and --backend tailscale avoids the block. Use potato share for a pilot or a lab meeting, and a cloud host for any task a participant might come back to the next day.
Deploying to the cloud with one command
potato deploy up takes the config you already have, creates the server, gets an HTTPS certificate, uploads the project, starts the task and prints the URL. Install the extra for your target, then look at the plan before running it:
pip install 'potato-annotation[deploy]' # most targets
pip install 'potato-annotation[deploy-aws]' # the three AWS targets
pip install 'potato-annotation[deploy-openstack]' # Jetstream2 and other OpenStack clouds
potato deploy up myproject/config.yaml --provider aws --dry-run
potato deploy up myproject/config.yaml --provider aws--dry-run needs no account. It prints every resource the deploy would create, the monthly cost, and any setting in your config that is risky on a public server. Without --dry-run, Potato shows the same plan and waits for you to confirm before it creates anything. Running up again on the same config updates the existing deployment, and after the first up, status, logs, pull and destroy need no --provider.
The thirteen cloud targets
Potato 2.10.0 deploys to thirteen cloud targets. They differ most in whether the disk survives a restart, which decides whether a deploy needs the backup described in the next section.
--provider | What it creates | Monthly cost | Disk survives a restart |
|---|---|---|---|
aws | an AWS Lightsail VM, 2 GB | $12 | yes |
aws-ec2 | an EC2 t4g.small VM with an Elastic IP | about $18 | yes |
aws-ecs | a container on ECS Express Mode | about $45-70 | no, needs --backup |
openstack | a VM on Jetstream2 or another OpenStack cloud | free with an allocation | yes |
hetzner | a Hetzner Cloud VM, 2 vCPU and 4 GB | about €6 | yes |
vultr | a Vultr VM, 1 vCPU and 2 GB | $10 | yes |
linode | an Akamai Linode VM, 1 vCPU and 2 GB | $12 | yes |
digitalocean | a DigitalOcean Droplet, 2 vCPU and 2 GB | $18 | yes |
fly | a Fly.io Machine with a 1 GB volume | about $6 | yes |
railway | a Railway service with a volume | billed by use, usually $10-20 | yes |
render | a Render web service | free, or $7 plus disk on starter | with a paid disk only |
heroku | one Heroku Basic dyno | $7 | no, needs --backup |
huggingface | a Docker Space and a private dataset for the annotations | a PRO plan ($9) or a Team plan | no, backed up to the dataset |
The seven VM targets (aws, aws-ec2, openstack, hetzner, vultr, linode and digitalocean) are set up the same way. Each gets a deploy key generated for that deployment, a firewall that opens only ports 22, 80 and 443, Caddy with a Let's Encrypt certificate, and Potato as a systemd service, and potato deploy logs and pull work on all of them. Fly, Railway, Render and ECS Express run the published image and download your project when the container starts. Railway, Render and ECS Express fetch it from the backup storage, so all three need --backup, and a Railway or Render service with a disk needs it too. On Fly, a project under 512 KB travels inside the Machine's configuration and needs no storage.
Google Cloud Run, Azure Container Apps and AWS App Runner are not targets. The storage that Cloud Run and Azure Container Apps offer cannot hold a SQLite database safely, and App Runner closed to new customers on 30 April 2026. On Azure, a VM running the Docker image works.
Backups for hosts that wipe their disk
On Heroku, ECS Express, Render's free tier and HuggingFace Spaces, the disk is wiped when the server restarts. --backup copies the annotation output and snapshots of the project databases to a HuggingFace dataset or an S3 bucket every five minutes, and restores them when a server starts with an empty disk:
potato deploy up myproject/config.yaml --provider heroku --backup hf --hf-token hf_...
potato deploy up myproject/config.yaml --provider heroku --backup s3 --s3-bucket my-bucketHeroku and ECS Express refuse to deploy without --backup, unless --demo declares the annotations disposable. The restore brings back the account list along with the annotations, so annotators sign in with the same passwords and continue where they stopped, and it never overwrites annotations already on the disk. --s3-endpoint sends the S3 backup to Cloudflare R2, Backblaze B2, MinIO or a university object store. The backup works on the VM targets too, where it keeps a second copy of the data off the server.
Outside potato deploy, a backup block in the config does the same job on a server you run yourself. Install the hosting extra, which provides the HuggingFace and S3 clients, set HF_TOKEN in the server's environment, and add this block to an existing config:
backup:
schedule_minutes: 5
restore_on_boot: true
sinks:
- type: huggingface
repo_id: lab/pilot-annotationsPulling annotations before destroy
potato deploy pull downloads everything a deployment has collected into a timestamped directory and checks what arrived, and potato deploy destroy removes the server. Run them in that order:
potato deploy pull myproject/config.yaml
potato deploy destroy myproject/config.yamldestroy refuses to remove a deployment that has never been pulled, and a pull that returns zero files does not count. The pull copies project.sqlite through SQLite's backup command rather than as a file, because a file copy of a database in WAL mode can be missing recent work. On Fly and Railway, destroying the app deletes its volume, so the pull is the only copy unless you also ran a backup.
Deploy buttons for other people's accounts
potato deploy button writes the files a hosting platform reads to offer a one-click deploy from your git repository, and prints the README badge. Use it when collaborators, students or another lab should run your task in their own accounts:
potato deploy button studies/pilot/config.yaml --target heroku \
--backup hf --hf-backup-repo lab/pilot-annotationsThe targets are heroku, render, aws and railway. The aws target writes a CloudFormation "Launch Stack" template that creates a Lightsail instance, and for railway the command prints the steps, because Railway publishes templates from its dashboard. Your config.yaml is left unchanged, and the command writes a copy beside it, potato.deploy.yaml, with deployment settings applied. No secrets go into the repository. Heroku, Render and the AWS template generate the session and admin keys at deploy time. The Heroku, Render and AWS buttons require --backup, and the person deploying supplies its credentials.
Further reading
Each target has its own page in the Potato documentation:
- Installing and running Potato and Deploying a task, the full
potato deploylifecycle - AWS: Lightsail, EC2 and ECS Express, with the IAM permissions each needs
- Jetstream2 and OpenStack, including how to request an allocation
- Hetzner, Vultr and Linode and DigitalOcean
- Fly.io, Railway, Render, Heroku and HuggingFace Spaces
- Backups, Getting annotations back and Deploy buttons
- Sharing a task on a temporary URL
On this site, Production Setup covers running Potato under gunicorn and Docker on a server you manage, and Reverse Proxy covers serving it under a URL path prefix.