Add Docker / cloud-friendly deploy path
Context
Today, x402chat is deployed on VPS-style infrastructure via setup.sh (installs to venv, configures systemd service, and optionally nginx). This works well for traditional servers, but many hosting platforms (Fly.io, Railway, Render, etc.) expect container images. This issue adds first-class Docker support without replacing the existing VPS path—both should coexist.
A cloud platform deployment should be as simple as:
docker build . && docker run -e HELIUS_API_KEY=... -e ENCRYPTION_KEY=... -v data:/app/data myapp
And local testing should require no nginx or systemd knowledge:
docker compose up
curl http://localhost:8765/health
Goals
- Single
Dockerfile that produces a runnable production image of the FastAPI app.
docker-compose.yml for local development parity (contributors can test without installing nginx/systemd).
- Platform compatibility with managed PaaS (at least one sample config for Fly.io, Railway, or Render).
- Persistent SQLite via volume mount (e.g.,
./data:/app/data in compose, or /app/data volume in production).
- No TLS in the image (TLS is terminated by the host platform). No nginx needed in the container.
- Healthcheck wired to the existing
/health endpoint (confirmed at app.py:1326).
- Non-root user for security.
- Minimal image size (target <300 MB).
Proposed Dockerfile shape
# Multi-stage build
FROM python:3.10-slim as builder
# Use uv or pip-tools to lock + install dependencies into a temp venv
FROM python:3.10-slim as runtime
# Copy app files: app.py, static/, privacy_scorer.py, requirements.txt
# Create non-root user (e.g., appuser:appuser)
# Set working directory to /app
# EXPOSE 8765
# CMD: run uvicorn directly for proper signal handling (not via app.py CLI)
Key points:
- Lock dependencies once at build time (reproducible).
- Runtime image copies only the installed dependencies + app code.
- Non-root user prevents container escape scenarios.
uvicorn app:app --host 0.0.0.0 --port 8765 for proper SIGTERM handling (unlike app.py server which may delay shutdown).
docker-compose.yml shape
version: '3.9'
services:
app:
build: .
ports:
- "8765:8765"
env_file: .env
environment:
DATABASE_URL: sqlite:////app/data/donations.db
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8765/health"]
interval: 30s
timeout: 5s
retries: 3
Notes:
env_file: .env loads secrets (HELIUS_API_KEY, ENCRYPTION_KEY, etc.) from the local .env.
DATABASE_URL is a 4-slash path because it's inside the container (sqlite:////app/data/donations.db = absolute path /app/data/donations.db).
- Volume
./data:/app/data persists SQLite across docker compose down && docker compose up.
- Healthcheck is optional but recommended for orchestration platforms.
App-side changes (minimal)
1. Confirm DATABASE_URL override works
Already implemented at app.py:891:
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///donations.db")
Document in .env.example or docs/deploy-docker.md:
# For Docker deployments, use an absolute path inside the container:
# DATABASE_URL=sqlite:////app/data/donations.db
2. Cookie security behind TLS-terminating proxies
The app already respects ENVIRONMENT=production (line 733) and validates origin headers. The cookie secure=True will work if:
- The platform sets
X-Forwarded-Proto: https (nginx does this; so do Fly.io and Railway).
- The app trusts that header (confirm in FastAPI middleware).
Document: "When running behind a reverse proxy, ensure it sets X-Forwarded-Proto: https. Fly.io, Railway, and Render all do this automatically."
3. Brief deployment docs
Add a short docs/deploy-docker.md or a "Docker" section in the README covering:
- Env var checklist for production:
HELIUS_API_KEY, ENCRYPTION_KEY, ENVIRONMENT=production, PRODUCTION_ORIGIN.
- Volume setup: Production systems must mount
/app/data to a persistent storage (e.g., Fly.io volumes, Railway persistent disks).
- Example Fly.io
fly.toml (stub):
[build]
dockerfile = "Dockerfile"
[[services]]
internal_port = 8765
protocol = "tcp"
[[services.ports]]
port = 443
handlers = ["http", "tls"]
[[mounts]]
source = "data"
destination = "/app/data"
Out of scope
- Postgres migration: SQLite is fine for single-replica deployments (which is the current model). If multi-replica is needed later, that's a separate DB decision. Document the SQLite limitation.
- Removing VPS path:
setup.sh, systemd service, and nginx config stay. Existing users are unaffected.
- Multi-replica deployments: SQLite doesn't support concurrent writers. Docker doesn't change this limit.
Acceptance criteria
Add Docker / cloud-friendly deploy path
Context
Today, x402chat is deployed on VPS-style infrastructure via
setup.sh(installs to venv, configures systemd service, and optionally nginx). This works well for traditional servers, but many hosting platforms (Fly.io, Railway, Render, etc.) expect container images. This issue adds first-class Docker support without replacing the existing VPS path—both should coexist.A cloud platform deployment should be as simple as:
And local testing should require no nginx or systemd knowledge:
Goals
Dockerfilethat produces a runnable production image of the FastAPI app.docker-compose.ymlfor local development parity (contributors can test without installing nginx/systemd)../data:/app/datain compose, or/app/datavolume in production)./healthendpoint (confirmed atapp.py:1326).Proposed Dockerfile shape
Key points:
uvicorn app:app --host 0.0.0.0 --port 8765for proper SIGTERM handling (unlikeapp.py serverwhich may delay shutdown).docker-compose.yml shape
Notes:
env_file: .envloads secrets (HELIUS_API_KEY, ENCRYPTION_KEY, etc.) from the local.env.DATABASE_URLis a 4-slash path because it's inside the container (sqlite:////app/data/donations.db= absolute path/app/data/donations.db)../data:/app/datapersists SQLite acrossdocker compose down && docker compose up.App-side changes (minimal)
1. Confirm
DATABASE_URLoverride worksAlready implemented at
app.py:891:Document in
.env.exampleordocs/deploy-docker.md:2. Cookie security behind TLS-terminating proxies
The app already respects
ENVIRONMENT=production(line 733) and validates origin headers. The cookiesecure=Truewill work if:X-Forwarded-Proto: https(nginx does this; so do Fly.io and Railway).Document: "When running behind a reverse proxy, ensure it sets
X-Forwarded-Proto: https. Fly.io, Railway, and Render all do this automatically."3. Brief deployment docs
Add a short
docs/deploy-docker.mdor a "Docker" section in the README covering:HELIUS_API_KEY,ENCRYPTION_KEY,ENVIRONMENT=production,PRODUCTION_ORIGIN./app/datato a persistent storage (e.g., Fly.io volumes, Railway persistent disks).fly.toml(stub):Out of scope
setup.sh, systemd service, and nginx config stay. Existing users are unaffected.Acceptance criteria
docker build -t x402chat:latest .succeeds and produces an image under 300 MB.docker compose upboots andcurl http://localhost:8765/healthreturns 200 (JSON with"status": "ok").docker compose down && docker compose up, donation history is intact (SQL query shows records).docker run ... whoami).HELIUS_API_KEY+ENCRYPTION_KEY); other vars have sane defaults.docs/deploy-docker.md) with env var checklist and sample Fly.io config.setup.shflow.