Configuration
Environment variables and server setup
Overview
LinkedRecords is configured primarily through environment variables. This guide covers all available configuration options. For a step-by-step deployment walkthrough, see Self-Hosting.
Quick Start
For local development, download the Docker Compose file from the repository and start the stack:
curl -fsSLO https://raw.githubusercontent.com/wolfoo2931/linkedrecords/main/docker-compose.yml
docker compose upThis starts PostgreSQL and LinkedRecords on port 6543 with a built-in mock OIDC
provider (AUTH_DEV_MODE). The test accounts alice@example.com,
bob@example.com, and charlie@example.com are available with one-click login.
The Docker Compose setup is for local development only. It includes a mock OIDC provider with test accounts and should not be used in production.
Server
| Variable | Description | Default |
|---|---|---|
PORT | Port the server listens on | 6543 |
SERVER_BASE_URL | Public URL of the LinkedRecords server | - |
FRONTEND_BASE_URL | URL of the frontend application (required; also used as CORS origin fallback and for OIDC redirects) | - |
HTTPS | Set to true to terminate TLS in the Node.js server itself | false |
SSL_KEY | PEM-encoded private key (required if HTTPS=true) | - |
SSL_CRT | PEM-encoded certificate (required if HTTPS=true) | - |
SERVER_BASE_URL=https://api.your-app.com
FRONTEND_BASE_URL=https://your-app.comMost deployments terminate TLS at a reverse proxy or load balancer and leave
HTTPS unset.
Database Configuration
LinkedRecords uses PostgreSQL for data storage. The connection is configured with the standard PostgreSQL environment variables:
| Variable | Description | Default |
|---|---|---|
PGHOST | PostgreSQL host | localhost |
PGPORT | PostgreSQL port | 5432 |
PGUSER | Database user | - |
PGPASSWORD | Database password | - |
PGDATABASE | Database name | - |
PGHOST=localhost
PGPORT=5432
PGUSER=linkedrecords
PGPASSWORD=your-secure-password
PGDATABASE=linkedrecordsThe database schema is created automatically on server start; no separate migration step is needed.
For tests and quick experiments without a PostgreSQL server, an embedded PGlite database can be used instead:
| Variable | Description | Default |
|---|---|---|
USE_PGLITE | Set to true to use embedded PGlite instead of PostgreSQL | false |
PGLITE_DATA_DIR | Data directory for PGlite (setting this also enables PGlite) | in-memory |
Authentication (OIDC)
LinkedRecords supports any OpenID Connect compliant identity provider (Auth0, Okta, Keycloak, ...):
| Variable | Description | Required |
|---|---|---|
AUTH_ISSUER_BASE_URL | OIDC provider URL | Yes |
AUTH_CLIENT_ID | OAuth client ID | Yes |
AUTH_CLIENT_SECRET | OAuth client secret | Confidential client mode |
AUTH_COOKIE_SIGNING_SECRET | Secret for signing session cookies | Confidential client mode |
AUTH_TOKEN_AUDIENCE | Expected aud claim of bearer tokens | Public client mode |
ALLOW_HTTP_AUTHENTICATION_HEADER | Set to true to accept bearer tokens via the Authorization header | Public client mode |
AUTH_IDP_LOGOUT | Set to true to also destroy the session at the identity provider on logout | No |
AUTH_DEV_MODE | Set to true to mount a built-in mock OIDC provider at /dev-oidc with one-click test accounts. Development only. | No |
Confidential Client Mode
For same-domain setups where the frontend and backend share a domain, the session is kept in an HttpOnly cookie:
AUTH_ISSUER_BASE_URL=https://your-tenant.auth0.com/
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret
AUTH_COOKIE_SIGNING_SECRET=a-long-random-secret-stringPublic Client Mode
For cross-domain setups (SPA on a different domain than the API), the SPA stores the access token and sends it as a bearer token:
AUTH_ISSUER_BASE_URL=https://your-tenant.auth0.com/
AUTH_CLIENT_ID=your-client-id
AUTH_TOKEN_AUDIENCE=your-api-audience
ALLOW_HTTP_AUTHENTICATION_HEADER=trueCORS Configuration
For cross-domain access, configure allowed origins. If CORS_ORIGIN is not
set, FRONTEND_BASE_URL is used.
| Variable | Description | Default |
|---|---|---|
CORS_ORIGIN | Allowed origin as a single URL string or JSON array of URLs | value of FRONTEND_BASE_URL |
CORS_ORIGIN='["https://your-app.com", "https://staging.your-app.com"]'Redis Configuration (Optional)
Redis is not required for a single-instance deployment. Configure it when running multiple LinkedRecords instances behind a load balancer, so real-time updates reach clients connected to other instances (Socket.IO Redis Streams adapter):
| Variable | Description | Default |
|---|---|---|
REDIS_HOST | Redis host; setting this enables the Redis adapter | - |
REDIS_USERNAME | Redis username | - |
REDIS_PASSWORD | Redis password | - |
REDIS_HOST=redis.your-provider.com
REDIS_USERNAME=default
REDIS_PASSWORD=your-redis-passwordS3/MinIO Configuration (Optional)
For blob storage, configure S3-compatible object storage:
| Variable | Description | Default |
|---|---|---|
S3_ENDPOINT | S3-compatible endpoint hostname | - |
S3_PORT | Endpoint port | - |
S3_BUCKET | Bucket name (must already exist) | - |
S3_ACCESS_KEY | Access key ID | - |
S3_SECRET_KEY | Secret access key | - |
S3_USE_SSL | Use TLS when talking to the endpoint (true/false) | true |
S3_COPY_FROM_BL_ATTRIBUTE_TABLE | One-time migration: copy existing blobs from PostgreSQL to S3 on startup | false |
S3_ENDPOINT=localhost
S3_PORT=9000
S3_BUCKET=linkedrecords
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_USE_SSL=falseIf S3 is not configured, blob values are stored in PostgreSQL (with reduced performance for large files). Configuring S3 is recommended.
Quota Configuration
Control storage limits per accountable user or organization:
| Variable | Description | Default |
|---|---|---|
DEFAULT_STORAGE_SIZE_QUOTA | Default storage quota in MB | 500 |
QUOTA_COUNT_KV_RECORDS | Set to true to count key-value record storage against the accountee's quota | false |
QUOTA_COUNT_LT_RECORDS | Set to true to count long-text record storage against the accountee's quota | false |
DEFAULT_STORAGE_SIZE_QUOTA=500
QUOTA_COUNT_KV_RECORDS=true
QUOTA_COUNT_LT_RECORDS=trueBlob storage always counts against the quota. See Quotas for how quota accounting works.
Payment Integration (Optional)
For paid quota upgrades with Paddle:
| Variable | Description |
|---|---|
PADDLE_API_KEY | Paddle API key |
PADDLE_API_URL | Paddle API URL (e.g. https://sandbox-api.paddle.com for testing) |
PADDLE_NOTIFICATION_SECRET | Webhook signing secret used to verify /payment_events notifications |
PADDLE_API_KEY=your-paddle-api-key
PADDLE_API_URL=https://api.paddle.com
PADDLE_NOTIFICATION_SECRET=webhook-signing-secretPerformance Tuning
| Variable | Description | Default |
|---|---|---|
ENABLE_AUTH_RULE_CACHE | Set to true to cache authorization lookups in memory. Can require a lot of memory. | false |
SHORT_LIVED_ACCESS_TOKEN_SIGNING | A signing secret. When set, short-lived signed read tokens are issued with query results and used to authorize real-time subscriptions, reducing database load. | not set |
ENABLE_AUTH_RULE_CACHE=true
SHORT_LIVED_ACCESS_TOKEN_SIGNING=a-long-random-secret-stringComplete Example Configuration
Development
The simplest way to get these is docker compose up (see Quick Start above).
Equivalent manual configuration:
# Database
PGHOST=localhost
PGPORT=5432
PGUSER=linkedrecords
PGPASSWORD=linkedrecords
PGDATABASE=linkedrecords
# Auth (built-in mock OIDC provider)
AUTH_DEV_MODE=true
AUTH_ISSUER_BASE_URL=http://localhost:6543/dev-oidc
AUTH_CLIENT_ID=linkedrecords-dev
AUTH_TOKEN_AUDIENCE=linkedrecords-dev
ALLOW_HTTP_AUTHENTICATION_HEADER=true
# URLs
SERVER_BASE_URL=http://localhost:6543
FRONTEND_BASE_URL=http://localhost:6543
# CORS
CORS_ORIGIN='*'Production
# Database
PGHOST=db.your-provider.com
PGPORT=5432
PGUSER=linkedrecords
PGPASSWORD=secure-production-password
PGDATABASE=linkedrecords
# Auth (Auth0 example, confidential client mode)
AUTH_ISSUER_BASE_URL=https://your-tenant.auth0.com/
AUTH_CLIENT_ID=production-client-id
AUTH_CLIENT_SECRET=production-client-secret
AUTH_COOKIE_SIGNING_SECRET=production-secret-at-least-32-chars
# URLs
SERVER_BASE_URL=https://api.your-app.com
FRONTEND_BASE_URL=https://your-app.com
# S3
S3_ENDPOINT=s3.amazonaws.com
S3_BUCKET=your-linkedrecords-bucket
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=...
S3_USE_SSL=true
# CORS
CORS_ORIGIN='["https://your-app.com"]'
# Quotas
DEFAULT_STORAGE_SIZE_QUOTA=1000
QUOTA_COUNT_KV_RECORDS=true
QUOTA_COUNT_LT_RECORDS=true
# Performance
ENABLE_AUTH_RULE_CACHE=true
SHORT_LIVED_ACCESS_TOKEN_SIGNING=another-long-random-secretOIDC Provider Setup
Auth0
- Create an Application (Regular Web Application for confidential, SPA for public)
- Configure Allowed Callback URLs:
https://your-backend.com/callback - Configure Allowed Logout URLs:
https://your-app.com - Configure Allowed Web Origins:
https://your-app.com - Get the Client ID and Client Secret
Other Providers
LinkedRecords works with any standard OIDC provider:
- Okta: Use the OIDC metadata URL
- Keycloak: Configure a client with appropriate scopes
- Google: Create OAuth 2.0 credentials
Health Check
There is no dedicated health endpoint. GET /oidc/discovery is served without
authentication and is suitable as a liveness probe:
curl http://localhost:6543/oidc/discoveryLogging
LinkedRecords logs structured JSON to stdout using pino. Request and response headers are redacted. Use your platform's log collector to ship and filter the output.