Files
PocketVeto/DEPLOYING.md
Jack Levy 3d19cd571a chore: bind-mount postgres/redis data dirs, update docs for pocketveto.org
- docker-compose.yml: replace named volumes with ./postgres/data and ./redis/data bind mounts
- .gitignore: exclude postgres/ and redis/ data directories
- DEPLOYING.md: update clone URL to public PocketVeto repo
- UPDATING.md: fix paths (~/pocketveto), clone URL, webhook IDs

Authored by: Jack Levy
2026-03-15 02:17:37 -04:00

6.0 KiB
Raw Permalink Blame History

Deploying PocketVeto

Step-by-step guide for standing up the full stack on a fresh server.


Prerequisites

Server:

  • Linux (Ubuntu 22.04+ or Debian 12 recommended)
  • Docker Engine 24+ and Docker Compose v2 (docker compose — note: no hyphen)
  • At least 2 GB RAM (4 GB recommended if running an Ollama LLM locally)
  • Port 80 open to the internet (and 443 if you add SSL)

API keys you will need:

Key Where to get it Required?
DATA_GOV_API_KEY api.data.gov/signup — free, instant Yes
One LLM key (OpenAI / Anthropic / Gemini) Provider dashboard Yes (or use Ollama)
NEWSAPI_KEY newsapi.org — free tier (100 req/day) Optional

Google Trends (pytrends) needs no key.


1. Get the code

git clone https://git.jackhlevy.com/jack/PocketVeto.git pocketveto
cd pocketveto

2. Configure environment

cp .env.example .env
nano .env          # or your preferred editor

Minimum required values:

# Network
LOCAL_URL=http://YOUR_SERVER_IP        # or https://yourdomain.com if behind SSL
PUBLIC_URL=                            # leave blank unless you have a public domain

# Auth — generate with: python -c "import secrets; print(secrets.token_hex(32))"
JWT_SECRET_KEY=your-generated-secret

# Encryption key for sensitive prefs (generate once, never change after data is written)
ENCRYPTION_SECRET_KEY=  # generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# PostgreSQL
POSTGRES_USER=congress
POSTGRES_PASSWORD=your-strong-password
POSTGRES_DB=pocketveto

# Redis
REDIS_URL=redis://redis:6379/0

# Congress.gov + GovInfo (shared api.data.gov key)
DATA_GOV_API_KEY=your-api-key

# LLM — pick one
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini

Other providers (swap in place of the OpenAI block):

# Anthropic
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-4-6

# Gemini
LLM_PROVIDER=gemini
GEMINI_API_KEY=AIza...
GEMINI_MODEL=gemini-2.0-flash

# Ollama (local model — server must be running on the host)
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_MODEL=llama3.1

Optional extras:

NEWSAPI_KEY=your-newsapi-key           # enables richer news correlation
PYTRENDS_ENABLED=true                  # Google Trends; disable if hitting rate limits
CONGRESS_POLL_INTERVAL_MINUTES=30     # how often to check Congress.gov
# Email notifications (optional — requires SMTP relay, e.g. Resend)
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_USER=resend
SMTP_PASSWORD=re_your-api-key
SMTP_FROM=alerts@yourdomain.com

3. Build and start

docker compose up --build -d

This will:

  1. Pull base images (postgres, redis, nginx, node)
  2. Build the API, worker, beat, and frontend images
  3. Start all 7 containers
  4. Run alembic upgrade head automatically inside the API container on startup
  5. Seed the Celery Beat schedule in Redis

First build takes 38 minutes depending on your server. Subsequent builds are faster (Docker layer cache).


4. Verify it's running

docker compose ps

All services should show Up:

civicstack-api-1        Up
civicstack-beat-1       Up
civicstack-frontend-1   Up
civicstack-nginx-1      Up  0.0.0.0:80->80/tcp
civicstack-postgres-1   Up (healthy)
civicstack-redis-1      Up (healthy)
civicstack-worker-1     Up

Check the API health endpoint:

curl http://localhost/api/health
# → {"status":"ok","timestamp":"..."}

Open http://YOUR_SERVER_IP in a browser.


5. Create the admin account

Navigate to http://YOUR_SERVER_IP/register and create the first account.

The first registered account automatically becomes admin. All subsequent accounts are regular users. The admin account gets access to the Settings page with the pipeline controls, LLM switching, and user management.


6. Trigger initial data load

Log in as admin and go to Settings:

  1. Trigger Poll — fetches bills updated in the last 60 days from Congress.gov (~510 minutes to complete)
  2. Sync Members — syncs current Congress members (~2 minutes)

The Celery workers then automatically:

  • Fetch bill text from GovInfo
  • Generate AI briefs (rate-limited at 10/minute)
  • Fetch news articles and calculate trend scores

You can watch progress in Settings → Pipeline Status.


7. Optional: Domain + SSL

If you have a domain name pointing to the server, add an SSL terminator in front of nginx. The simplest approach is Caddy as a reverse proxy:

# Install Caddy
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy

/etc/caddy/Caddyfile:

yourdomain.com {
    reverse_proxy localhost:80
}
systemctl reload caddy

Caddy handles HTTPS certificates automatically via Let's Encrypt.

After adding SSL, update .env:

PUBLIC_URL=https://yourdomain.com

Then rebuild the API so the new URL is used in notification payloads:

docker compose up --build -d api

Useful commands

# View logs for a service
docker compose logs --tail=50 api
docker compose logs --tail=50 worker
docker compose logs -f worker        # follow in real time

# Restart a service
docker compose restart worker

# Run a database query
docker compose exec postgres psql -U congress pocketveto

# Apply any pending migrations manually
docker compose exec api alembic upgrade head

# Open a Python shell inside the API container
docker compose exec api python

Troubleshooting

See TROUBLESHOOTING.md for common issues (502 errors after rebuild, wrong postgres user, frontend changes not showing, etc.).