Deployment¶
This page covers deploying uproot experiments for production use.
Self-hosted with nginx (recommended)¶
The recommended way to run uproot in production is behind an nginx reverse proxy. This setup gives you full control and works well on any VPS or dedicated server. We strongly recommend that you use a VPS with Debian 13+. (While uproot itself works flawlessly on Ubuntu, using Ubuntu is in general considered bad practice. uproot also works on OpenBSD.)
HTTPS required
uproot requires HTTPS in production. Many browser features (like the secure cookies needed for accessing the admin area) only work over HTTPS. Use Let's Encrypt with certbot to get free TLS certificates.
Running uproot¶
Start uproot in a tmux session so it persists after you disconnect:
Detach with Ctrl+B then D. Reattach later with tmux attach -t uproot.
uproot listens on port 8000 by default. Use --port to change it if needed.
nginx configuration¶
Configure nginx as a reverse proxy. The WebSocket upgrade headers are required for real-time features. The following example config (to be added within an existing http block) is battle-tested and has been proven to work reliably:
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
server {
server_name example.com; # Adjust this
listen 443 ssl;
listen 443 quic;
listen [::]:443 ssl;
listen [::]:443 quic;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
ssl_certificate PATH_TO_fullchain.pem; # Adjust this
ssl_certificate_key PATH_TO_privkey.pem; # Adjust this
location / {
proxy_pass http://127.0.0.1:8000; # Maybe adjust this
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Front-End-Https on;
proxy_set_header X-Forwarded-Protocol https;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Url-Scheme https;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
Hosting in a subdirectory¶
uproot can run in a subdirectory instead of at the root. Set the UPROOT_SUBDIRECTORY environment variable and adjust your nginx location block:
location /u/ { # ONLY THIS CHANGES
proxy_pass http://127.0.0.1:8000; # same as above
# ... same proxy settings as above ...
}
Fly.io with SQLite¶
Fly.io is an excellent platform for deploying uproot experiments. It provides excellent support for long-running WebSocket connections, works seamlessly with uproot's architecture, and uses SQLite for simple, reliable data persistence.
Why Fly.io?
- Full support for WebSocket connections and real-time features
- Works with uproot's default SQLite database (no additional setup needed)
- Simple pricing with generous free tier
- Regional deployment for lower latency
- Native support for persistent volumes
Prerequisites¶
- Create a Fly.io account
- Install the Fly CLI:
- Log in to Fly:
Deploy your experiment¶
Navigate to your uproot project directory and run:
Fly will detect your Python application and guide you through the setup. When prompted:
- Choose an app name or let Fly generate one
- Choose a region close to your participants
- Decline PostgreSQL when asked (uproot uses SQLite by default)
- Decline Redis when asked
This creates a fly.toml configuration file. Edit it to ensure the correct settings:
app = "your-app-name"
primary_region = "iad" # or your chosen region
[build]
builder = "paketobuildpacks/builder:base"
[env]
PORT = "8080"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "off" # Keep WebSocket connections alive
auto_start_machines = true
min_machines_running = 1
[[vm]]
memory = "512mb"
cpu_kind = "shared"
cpus = 1
Adding persistent storage for SQLite¶
Create a persistent volume to ensure your SQLite database survives app restarts:
Update your fly.toml to mount the volume:
Then modify your main.py to use the persistent storage location:
import os
# Add this near the top of your main.py
if os.getenv("FLY_APP_NAME"):
# Running on Fly.io, use persistent volume
os.environ["UPROOT_DB_PATH"] = "/data/uproot.sqlite3"
Deploy¶
Deploy your application:
After deployment completes, Fly will show your app's URL. Open it in your browser:
Setting up the admin account¶
Since auto-login only works on localhost, you'll need to set a password for the admin account. Edit your main.py:
import uproot.deployment as upd
# Replace the ... with a secure password
upd.ADMINS["admin"] = "your-secure-password-here"
Then redeploy:
Monitoring and logs¶
View your app's logs:
Check app status:
Access the admin interface at https://your-app-name.fly.dev/admin/
Railway¶
Railway is another excellent option that auto-detects uproot's Procfile and requires minimal configuration. It's particularly good for quick deployments.
Advantages¶
- Zero-config deployment (detects
Procfileautomatically) - Built-in PostgreSQL support if needed
- Simple GitHub integration
Quick start¶
- Sign up at railway.app
- Connect your GitHub repository
- Railway auto-deploys your app
- Set
UPROOT_ORIGINenvironment variable to your Railway domain
Heroku¶
Heroku provides a straightforward way to deploy uproot experiments with PostgreSQL. Note that Heroku requires PostgreSQL and doesn't support persistent SQLite storage.
WebSocket connection instability
Heroku's router has a 55-second idle timeout for streaming connections including WebSockets. If no data is transmitted for 55 seconds, the connection is terminated. This can cause issues in uproot experiments where participants may be inactive for longer periods (reading instructions, thinking, waiting for others). Consider using Fly.io or Railway for more stable WebSocket support.
Prerequisites¶
- Create a Heroku account
- Install the Heroku CLI
- Log in to Heroku:
Deploy your experiment¶
First, edit your pyproject.toml to include PostgreSQL support:
Create an app.json file in your project root:
{
"name": "Uproot Project",
"description": "An uproot-based web application for behavioral science experiments",
"keywords": ["python", "uproot", "experimental-economics", "behavioral-science"],
"buildpacks": [
{
"url": "heroku/python"
}
],
"formation": {
"web": {
"quantity": 1,
"size": "basic"
}
},
"addons": [
{
"plan": "heroku-postgresql:essential-0"
}
],
"env": {
"UPROOT_ORIGIN": {
"description": "The public URL of your app (e.g., https://your-app-name.herokuapp.com). Auto-detected if you enable 'heroku labs:enable runtime-dyno-metadata'. Override for custom domains.",
"required": false
}
}
}
Then deploy: