FastAPI + vanilla JavaScript financial dashboard with PostgreSQL persistence, JWT auth, admin/demo roles, and Yahoo Finance price loading.
- Features
- Deploy on Coolify
- Environment Variables
- First Login
- Operational Notes
- Troubleshooting
- Local Development
- API Summary
- References
- Admin account with full access to watchlists, tickers, settings, tag colors, cache clearing, and password changes.
- Demo account with read-only access.
- PostgreSQL schema creation and seed data on first startup.
- JWT login/session restore.
- Server-side authorization for all write endpoints.
- Rate limits on login and price-fetching endpoints.
- Coolify/Nixpacks deployment files:
.python-versionrequirements.txtnixpacks.toml
This app is designed for a Coolify application resource plus a separate Coolify PostgreSQL resource.
- Push this repository to the Git provider connected to Coolify.
- Create and deploy a PostgreSQL resource in the same Coolify project/environment.
- Copy the PostgreSQL internal connection URL.
- Create a Coolify application from this repository.
- Use the default Nixpacks build pack.
- Keep Is it a static site? disabled.
- Set:
Base Directory: /
Port Exposes: 8000
Start Command: python server.py
Health Check Path: /api/auth/demo-info
nixpacks.toml already defines the start command, but setting it in Coolify is also fine. Coolify normally provides PORT from Port Exposes; the server falls back to 8000 if PORT is absent.
Use the PostgreSQL internal URL for DATABASE_URL, for example:
DATABASE_URL=postgresql://postgres:password@postgresql-service:5432/postgresDo not use localhost for DATABASE_URL in Coolify. Inside the app container, localhost means the app container itself, not the PostgreSQL container.
Required:
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DATABASE
MARKETDECK_JWT_SECRET=replace-with-a-long-random-secret
MARKETDECK_ADMIN_EMAIL=admin@example.com
MARKETDECK_ADMIN_PASSWORD=replace-with-a-strong-passwordOptional:
MARKETDECK_DB_CONNECT_RETRIES=30
MARKETDECK_DB_CONNECT_RETRY_DELAY=2
MARKETDECK_PRICE_CACHE_TTL_SECONDS=3600
PORT=8000Recommended Coolify settings:
| Setting | Value | Notes |
|---|---|---|
| Build Variable | Yes | Coolify enables this by default. The app only needs these at runtime, so disabling buildtime is optional hardening. |
| Runtime Variable | Yes | Required. The server reads these when the container starts. |
| Literal | No | Use Yes only when the value contains $... text that must not be interpolated. |
| Multiline | No | None of these values should be multiline. |
Generate a JWT secret locally:
openssl rand -hex 32On first successful startup, the app creates tables, seeds default dashboard data, inserts the demo user, and inserts the admin user.
Checklist:
- Open the public app URL.
- Click Login as Demo and confirm the dashboard loads.
- Log out.
- Log in with
MARKETDECK_ADMIN_EMAILandMARKETDECK_ADMIN_PASSWORD. - Confirm admin controls are visible.
- Change the admin password in the app if desired.
Changing MARKETDECK_ADMIN_PASSWORD later does not overwrite an existing database user. Use the in-app password change flow after the first seed.
Dashboard seed data is first-deploy only. If the watchlists table already has rows, the app skips watchlist/ticker/tag/settings seeding.
Admin users are inserted with ON CONFLICT DO NOTHING, so redeploys do not reset the admin password. Demo user seeding is also idempotent.
The server retries the PostgreSQL connection during startup. This helps when Coolify starts the app while PostgreSQL is still becoming ready.
The demo account is a real database user with role demo, but it does not use public credentials. The Login as Demo button calls POST /api/auth/demo-login and receives a read-only demo session.
Demo users can browse data and fetch prices, but write endpoints return 403.
Rate limiting is in memory and only protects the Yahoo Finance proxy endpoint:
POST /api/prices:120/minute
If you scale horizontally, move rate-limit storage to a shared backend such as Redis.
Yahoo Finance responses are cached in PostgreSQL per account and ticker. This means the demo account shares cached prices across devices, while admin and demo sessions do not share price-cache entries with each other.
The default cache TTL is 1 hour. Override it with:
MARKETDECK_PRICE_CACHE_TTL_SECONDS=3600Admins can clear the server-side cache through:
DELETE /api/prices/cache
Check Coolify logs for missing env vars, dependency installation errors, or PostgreSQL connection failures.
Required env vars:
DATABASE_URL
MARKETDECK_JWT_SECRET
MARKETDECK_ADMIN_EMAIL
MARKETDECK_ADMIN_PASSWORD
Check that:
DATABASE_URLuses the Coolify internal PostgreSQL URL.- The app and database are in the same Coolify project/network.
- The hostname is the PostgreSQL service hostname, not
localhost. - The PostgreSQL resource is running.
Use:
/api/auth/demo-info
This endpoint also checks database connectivity. If it fails, inspect app logs first.
Check that the credentials match the database user. If the admin user already existed, changing MARKETDECK_ADMIN_PASSWORD in Coolify will not reset it.
Local development requires PostgreSQL.
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export DATABASE_URL="postgresql://user:password@localhost:5432/marketdeck"
export MARKETDECK_JWT_SECRET="$(openssl rand -hex 32)"
export MARKETDECK_ADMIN_EMAIL="admin@example.com"
export MARKETDECK_ADMIN_PASSWORD="change-me"
python server.pyOpen:
http://localhost:8000
Public:
GET /api/auth/demo-infoPOST /api/auth/loginPOST /api/auth/demo-login
Authenticated admin or demo:
GET /api/auth/meGET /api/initGET /api/settingsPOST /api/prices
Admin only:
PUT /api/auth/passwordPUT /api/settings/{key}POST /api/listsPUT /api/lists/{slug}DELETE /api/lists/{slug}POST /api/lists/{slug}/tickersPUT /api/tickers/{id}DELETE /api/tickers/{id}PUT /api/tag-colors/{tag}DELETE /api/tag-colors/{tag}DELETE /api/prices/cache
Protected endpoints require:
Authorization: Bearer <token>