Portal Deployment
Building
cd /development/rockfish/ndr
# Build Docker image
./scripts/build-portal.sh
# Push to DigitalOcean Container Registry
./scripts/build-portal.sh --push
# Build local binary
./scripts/build-portal.sh --install
Environment Variables
All configuration is via environment variables (.env file or platform settings).
Required
| Variable | Description |
|---|---|
PORTAL_DOMAIN | Public URL (e.g., https://portal.rockfishndr.com) |
STRIPE_SECRET_KEY | Stripe API secret key (sk_test_... or sk_live_...) |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret (whsec_...) |
STRIPE_PRICE_BASIC | Stripe Product or Price ID for Basic tier |
STRIPE_PRICE_PROFESSIONAL | Stripe Product or Price ID for Professional tier |
STRIPE_PRICE_ENTERPRISE | Stripe Product or Price ID for Enterprise tier |
LICENSE_SERVER_TOKEN | Bearer token for the license server |
Optional
| Variable | Default | Description |
|---|---|---|
BIND_ADDR | 0.0.0.0:8080 | Server bind address |
DATABASE_PATH | /var/lib/rockfish/portal.db | DuckDB file path |
LICENSE_SERVER_HOST | http://127.0.0.1:8080 | License server URL |
SMTP_HOST | — | SMTP server (emails logged if unset) |
SMTP_PORT | 587 | SMTP port (587=STARTTLS, 465=TLS, 25/1025=plain) |
SMTP_USER | — | SMTP username |
SMTP_PASSWORD | — | SMTP password |
SMTP_FROM | [email protected] | From address |
S3_BUCKET | — | S3 bucket for data persistence |
S3_REGION | — | S3 region |
S3_ENDPOINT | — | S3 endpoint (for DigitalOcean Spaces, MinIO) |
S3_ACCESS_KEY_ID | — | S3 access key |
S3_SECRET_ACCESS_KEY | — | S3 secret key |
S3_PORTAL_PREFIX | portal | S3 key prefix |
S3_FORCE_PATH_STYLE | false | Use path-style S3 URLs |
MAGIC_LINK_TTL_MINUTES | 15 | Magic link expiry |
PORTAL_DISABLED | false | Show “Coming Soon” page, disable purchasing |
DigitalOcean App Platform
1. Push Docker image
./scripts/build-portal.sh
./scripts/build-portal.sh --push
2. Create App
DigitalOcean Dashboard → Apps → Create App:
- Source: Container Registry →
rockfishnetworks/rockfish-portal:latest - HTTP Port:
8080 - Instance size: Basic ($5/mo)
3. Set environment variables
Add all required variables in the App Settings → Environment Variables.
4. Custom domain
App Settings → Domains → Add portal.rockfishndr.com
DNS: Add CNAME record pointing to your-app.ondigitalocean.app
5. Stripe webhook
Stripe Dashboard → Developers → Webhooks → Add:
- URL:
https://portal.rockfishndr.com/webhook/stripe - Events:
checkout.session.completed
Docker (self-hosted)
docker run -d \
--name rockfish-portal \
--env-file .env \
-e BIND_ADDR=0.0.0.0:8080 \
-p 8080:8080 \
rockfish-portal
Startup Connectivity Checks
On startup, the portal tests:
- License server —
GET {LICENSE_SERVER_HOST}/api/v1/health - S3 — attempts to list objects under the configured prefix
Results are logged to help diagnose configuration issues.
S3 Data Layout
s3://{bucket}/{prefix}/
├── users.parquet
├── licenses.parquet
├── pending_licenses.parquet
├── magic_links.parquet
└── reminders.parquet
Retry Logic
If the license server is unavailable when a payment is confirmed:
- License request is queued in
pending_licensestable - Background task retries every 60 seconds
- Up to 100 retries (~100 minutes)
- On success, license is moved to
licensestable and synced to S3