Installation
Requirements
- Node.js 24.x (LTS)
- npm 11+
- Docker Engine >= 20.10
- Docker Compose v2 (
docker compose) - Git
- Optional: Python 3.8+ and Ansible for kiosk appliance setup
Get the code
git clone https://github.com/CameronBrooks11/freeboard.git
cd freeboard
git checkout main
# Optional: align to repo Node baseline via .nvmrc
nvm use || nvm install
npm installEnvironment setup
Copy .env.example to .env and set real secrets.
For quick local development bootstrap, copy .env.dev to .env first.
Minimum production-safe values
# API runtime
FREEBOARD_RUNTIME_ENV=production
JWT_SECRET=replace-with-a-long-random-secret-at-least-32-chars
JWT_GATEWAY_SECRET=replace-with-a-long-random-gateway-secret-at-least-32-chars
GATEWAY_SERVICE_TOKEN=replace-with-a-long-random-gateway-service-token-at-least-32-chars
CREDENTIAL_ENCRYPTION_KEY=replace-with-base64-32-byte-key
# Optional runtime image tag pinning (default is latest)
FREEBOARD_UI_IMAGE_TAG=latest
FREEBOARD_API_IMAGE_TAG=latest
FREEBOARD_GATEWAY_IMAGE_TAG=latest
# Postgres for API (default runtime)
FREEBOARD_POSTGRES_URL=postgresql://postgres:replace-with-strong-password@postgres:5432/freeboard
# Postgres init credentials (docker-compose.postgres.yml)
POSTGRES_USER=postgres
POSTGRES_PASSWORD=replace-with-strong-postgres-password
POSTGRES_DB=freeboard
# Gateway egress allowlist (required in non-development runtime)
EGRESS_ALLOWED_HOSTS=api.open-meteo.com,api.coingecko.com"Outside development" means NODE_ENV is anything but development or test. docker-compose.yml sets production by default, while a bare node start with NODE_ENV unset counts as development and skips these checks.
Outside development, the API refuses to start while the three secrets, the credential key or the password inside FREEBOARD_POSTGRES_URL still holds its placeholder. The URL's password is refused in two cases:
- it is
postgres,password,secret,default,changemeorfreeboard; - it contains
replace-with,example,local-only,changemeorlocaldev.
A generated password will not hit these.
The API never reads POSTGRES_PASSWORD itself, so the URL's password has to match it. The Postgres image applies POSTGRES_PASSWORD only when it first initialises an empty data volume. On a database that already exists, changing both values in .env leaves the role on its old password, so set the new one in the database first:
docker exec -it freeboard-postgres psql -U postgres -c "ALTER ROLE postgres PASSWORD 'new-password';"docker-compose.postgres.yml publishes Postgres on 127.0.0.1 only. The API reaches it over the compose network, and Docker-published ports bypass host firewalls such as ufw. To expose it on another interface, set FREEBOARD_POSTGRES_BIND deliberately.
Secret storage/rotation patterns and incident response are documented in:
Optional local bootstrap admin
CREATE_ADMIN=true
ADMIN_EMAIL=admin@example.local
ADMIN_PASSWORD=ChangeMe123!After first successful login, set CREATE_ADMIN=false.
Outside development, the API refuses an ADMIN_PASSWORD that matches the rule above, including the one shown here and the .env.dev default, so pick a real one before the first production start.
ADMIN_PASSWORD is used only to create an admin when no user with ADMIN_EMAIL exists yet. Changing it later does not change that user's password. If an admin was ever created with a placeholder password, reset it through the password reset flow (adminIssuePasswordReset, then resetPassword).
Local development
cp .env.dev .env- Set bootstrap credentials if needed (
CREATE_ADMIN=true) - Run
npm run dev - Login once and disable bootstrap (
CREATE_ADMIN=false)
API env precedence:
- Process environment (shell/CI)
packages/api/.env(optional local override)- Repo root
.env - API defaults
Run with Docker Compose
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -dThe API applies any pending database migrations on startup, so a fresh database is provisioned automatically before the admin bootstrap and first login — no separate schema step is required. To manage the schema externally instead, set RUN_DB_MIGRATIONS=false and apply migrations yourself with npm run db:schema:apply.
Pinning examples:
# release pin
FREEBOARD_UI_IMAGE_TAG=v0.1.0
FREEBOARD_API_IMAGE_TAG=v0.1.0
FREEBOARD_GATEWAY_IMAGE_TAG=v0.1.0
# immutable build pin
FREEBOARD_UI_IMAGE_TAG=sha-abc1234
FREEBOARD_API_IMAGE_TAG=sha-abc1234
FREEBOARD_GATEWAY_IMAGE_TAG=sha-abc1234Rollback is tag-based: switch to the prior known-good v* or sha-* tags and redeploy.
Services:
- UI:
http://localhost:8080 - API (via UI reverse proxy):
http://localhost:8080/graphql(internal:freeboard-api:4001) - Gateway (via UI reverse proxy):
http://localhost:8080/gateway/http/fetch(internal:freeboard-gateway:8001)
Security checks before go-live
- Keep
FREEBOARD_RUNTIME_ENV=production - Use strong
JWT_SECRET(32+ chars) - Use strong
JWT_GATEWAY_SECRETandGATEWAY_SERVICE_TOKEN - Set valid
CREDENTIAL_ENCRYPTION_KEY(base64 32-byte key) - Keep
EGRESS_ALLOW_INSECURE_TLS=false - Keep
EGRESS_ALLOW_PRIVATE_DESTINATIONS=false - Set strict
EGRESS_ALLOWED_HOSTS - Set
API_TRUST_PROXY_HOPSandREALTIME_TRUST_PROXY_HOPScorrectly for your reverse-proxy topology - Keep
SECURITY_LIMITER_BACKENDaligned withDB_BACKENDandSECURITY_LIMITER_FAILURE_MODE=fail-closedin non-dev runtime - Keep
REALTIME_LIMITER_FAILURE_MODE=fail-closedunless degraded-mode fail-open is intentionally approved - Keep
CREATE_ADMIN=falseafter bootstrap - Use non-default Postgres credentials
- Follow Secrets Operations Runbook for setup/rotation/incident workflow
Security Rollout Runbook
For security-control changes (proxy trust headers/hops, limiter backend/failure-mode policy, gateway realtime limiter behavior), use:
This runbook defines staged deploy order (proxy -> API -> gateway), canary watch metrics, and rollback steps.
Release Readiness Runbook
For final PostgreSQL release hardening and pre-release checklist signoff, use: