Overview
Start Here
Installation
Tutorial
Architect App Builder
Todo List App
Beacon Beats Audio Store
UCAL App
Authoring
Dynamic Apps
Static Apps
dev_docs
Why Pharos Feels Different
Architect Builder
Framework
Artifacts & Layout
Configuration
Outbound Email
How to Use CLI
Contracts
YAML Authoring
Action Hooks and Middleware
Feature Transaction Primitives
Generated Surfaces
Declarative CRM
Input Validation
Identity and Tenancy
UI Surface Library
Social Connections
Payment Provider Management
Integration Adapters
Reference
Contract, UI, and Surface Reference
Styling UI Elements
Architect Media Artifacts
API Docs
Use Cases
Dynamic SPAs
Generated Admin and APIs
Scheduling and Marketplaces
Multi-App Hosting
Release Notes
Vision
Pharos Framework

← Back

Configuration

Pharos uses two levels of configuration. Shared runtime config controls the server, its listeners, and services available to all apps. App-local config sets per-app overrides such as mount path, state and log paths, database settings, and outbound email settings.

Configure Pharos

Pharos reads shared runtime config from the path selected by PHAROS_CONFIG. Shared-runtime commands such as pharos serve runtime shared --conf /etc/pharos/pharos.conf can also point at a config file explicitly. Packaged installs default to /etc/pharos/pharos.conf.

The config file uses key=value format.

Shared runtime config

A typical shared config file looks like this:

host=127.0.0.1
port=7323
# socket_path=/run/pharos/pharos.sock

base_url=app.example.com
apps_dir=/srv/pharos/apps

# Optional fixed worker sizing.
# worker_count=8
# worker_queue_capacity=256

log_path=/var/log/pharos/runtime.log
log_level=WARN

db_backend=sqlite
db_path=/var/lib/pharos/pharos.sqlite3
# db_host=127.0.0.1
# db_port=5432
# db_name=pharos
# db_user=pharos
# db_password_env=PHAROS_DB_PASSWORD
# db_tls_mode=require
# db_tls_root_cert=/etc/ssl/certs/ca-certificates.crt
# db_tls_cert=/etc/pharos/client.crt
# db_tls_key=/etc/pharos/client.key
# db_socket=/var/run/postgresql
# db_charset=utf8mb4
# db_options=application_name=pharos

# Outbound email is disabled by default until SMTP settings are uncommented.
# smtp_server=<your_primary_smtp_server>
# smtp_encryption=STARTTLS
# smtp_port=587
# smtp_mail_from=noreply@example.com
# smtp_login=<your_primary_smtp_login>
# smtp_pass=PHAROS_SMTP_PASS
# smtp_timeout_seconds=15
# smtp_max_message_bytes=1048576

# Optional secondary relay. Inactive until smtp2_* values are set.
# smtp2_server=<your_secondary_smtp_server>
# smtp2_encryption=STARTTLS
# smtp2_port=587
# smtp2_mail_from=noreply@example.com
# smtp2_login=<your_secondary_smtp_login>
# smtp2_pass=PHAROS_SMTP_PASS
# smtp2_timeout_seconds=15
# smtp2_max_message_bytes=1048576

# trusted_proxy_cidrs=127.0.0.1,10.0.0.0/8
# required_env=PHAROS_SMTP_PASS
# secret_env=PHAROS_DB_PASSWORD,PHAROS_SMTP_PASS
# backup_dir=/var/backups/pharos

Worker pool sizing

By default, Pharos sizes the worker pool from the host machine:

  • worker_count defaults to the number of online CPUs, clamped between 1 and 256
  • worker_queue_capacity defaults to floor(total_physical_memory / 8 MB), clamped between 32 and 8192
  • If physical memory cannot be detected, worker_queue_capacity falls back to 128

Set worker_count or worker_queue_capacity explicitly when you want fixed values instead of auto-detected ones.

Database-backed apps

Dynamic apps use a database by default.

  • db_backend=sqlite is the default packaged setup
  • The shared-runtime default SQLite path is /var/lib/pharos/pharos.sqlite3
  • The canonical per-app SQLite path for sqlite-backed dynamic apps is /var/lib/pharos/<app_id>.sqlite3
  • PostgreSQL can be configured with db_host, db_port, db_name, db_user, db_password_env, db_tls_mode, db_tls_root_cert, db_tls_cert, db_tls_key, db_socket, db_charset, and db_options
  • MySQL uses the same shared db_* contract and consumes db_socket, db_charset, and db_options when present
  • MongoDB uses db_host, db_port, db_name, db_user, db_password_env, db_tls_mode, db_tls_root_cert, db_tls_cert, db_tls_key, and db_options
  • db_password_env must be a valid environment variable name using letters, digits, and underscores, and it must not begin with a digit
  • db_tls_mode accepts only disable, allow, prefer, require, verify-ca, or verify-full
  • db_port must be a numeric TCP port when db_socket is not used
  • db_socket is consumed by PostgreSQL and MySQL socket-capable deployments
  • db_charset is consumed by MySQL session setup and may be ignored by other backends
  • db_options remains a backend-extension field for driver-specific connection options
  • Runtime scratch JSON exports are not the source of truth when a database backend is active
  • Redis is optional cache infrastructure, not a db_backend value

Redis cache

Redis is implemented as an optional runtime-wide cache layer. It is disabled by default, never becomes authoritative state, and degrades to cache miss when Redis is unavailable.

Common Redis settings:

redis_enabled=1
redis_host=127.0.0.1
redis_port=6379
redis_db_index=0
redis_cache_prefix=pharos
redis_connect_timeout_ms=500
redis_read_timeout_ms=300
redis_backoff_seconds=30
redis_backoff_max_seconds=300
redis_tls_mode=disable
redis_require_peer_name_match=1
redis_cache_sessions_enabled=1
redis_cache_template_source_enabled=0
redis_cache_route_match_enabled=0
redis_cache_config_allowlist_enabled=0

For non-loopback Redis, configure TLS and keep the Redis secret in the environment named by redis_auth_token_env. See Redis Cache Operation Guide for the full runtime cache contract.

Outbound email

Pharos resolves outbound email settings through the same app-over-runtime precedence model used for other dynamic config keys.

Supported keys include:

  • email=enabled|disabled
  • smtp_server
  • smtp_port
  • smtp_mail_from — required when outbound email is enabled
  • smtp_login
  • smtp_pass
  • smtp_encryption=STARTTLS|SSL
  • smtp_timeout_seconds
  • smtp_max_message_bytes
  • smtp2_server
  • smtp2_port
  • smtp2_mail_from
  • smtp2_login
  • smtp2_pass
  • smtp2_encryption=STARTTLS|SSL
  • smtp2_timeout_seconds
  • smtp2_max_message_bytes
  • base_url

Pharos accepts only these SMTP mode pairs:

  • smtp_encryption=STARTTLS with smtp_port=587
  • smtp_encryption=SSL with smtp_port=465
  • smtp2_encryption=STARTTLS with smtp2_port=587
  • smtp2_encryption=SSL with smtp2_port=465

When smtp2_* keys are unset or empty, the secondary relay stays inactive. When configured, Pharos tries the primary relay first and falls back to the secondary only on transient transport failures.

If base_url=app.example.com is set without a scheme, Pharos normalizes absolute verification links to https://app.example.com.

Delivery failures are written to the runtime or app log file.

Payment providers

The initial payment-provider interface is implemented with Stripe as the first concrete provider. Apps opt into in-app payment-provider management from their manifest. Runtime configuration only enables the operator-owned provider lane and supplies secret references:

payment_providers=stripe
stripe_client_id=<stripe-connect-client-id>
stripe_client_secret_env=PHAROS_STRIPE_CLIENT_SECRET
stripe_webhook_secret_env=PHAROS_STRIPE_WEBHOOK_SECRET
payment_provider_state_key_env=PHAROS_PAYMENT_PROVIDER_STATE_KEY
payment_provider_secret_key_env=PHAROS_PAYMENT_PROVIDER_SECRET_KEY
payment_provider_secret_key_version=v1
# During rotation only: retain one prior key until every encrypted row is rewrapped.
payment_provider_secret_previous_key_version=v0
payment_provider_secret_previous_key_env=PHAROS_PAYMENT_PROVIDER_SECRET_KEY_V0
# Optional local proof overrides. Production should use the default HTTPS Stripe endpoints.
# payment_provider_allow_loopback_http=false
# payment_provider_allow_unsigned_local_webhook=false
# payment_provider_allow_plaintext_local=false
# stripe_oauth_authorize_url=https://connect.stripe.com/oauth/authorize
# stripe_oauth_token_url=https://connect.stripe.com/oauth/token
# stripe_account_api_base_url=https://api.stripe.com/v1/accounts
# stripe_authenticated_account_api_url=https://api.stripe.com/v1/account
# stripe_oauth_deauthorize_url=https://connect.stripe.com/oauth/deauthorize
# stripe_checkout_sessions_url=https://api.stripe.com/v1/checkout/sessions
base_url=app.example.com/ucal

The environment variables named by the *_env settings must be present in the runtime service environment and must not be committed to app source. base_url must match the externally visible app origin and base path because provider callback and checkout return URLs are derived from it. App-owned provider management and checkout declarations live in data/payment_provider.yml; the runtime consumes that file through the app manifest.

payment_provider_secret_key_version identifies the active AES-256-GCM key. A rotation may declare one previous version and environment variable while existing rows are rewrapped; remove the previous-key settings only after that succeeds.

The Stripe endpoint override keys are optional. By default, Pharos uses the official HTTPS Stripe endpoints. HTTP endpoint overrides are accepted only for loopback addresses when payment_provider_allow_loopback_http=true, which is intended for local validation stubs and must stay disabled in production.

Saving provider credentials encrypts them but does not verify them. A successful connection test authenticates with Stripe and requires its account ID to match the configured account. Synthetic smoke credentials must produce failure feedback against the real service. A controlled local provider proof uses -payment-provider-proof controlled-success; it must explicitly configure the loopback endpoint overrides above. The default smoke expectation is rejection, and test credentials are revoked during cleanup even if an assertion fails.

See In-App Payment Provider Management for the app payment-provider contract, UCAL business-admin flow, and Beacon Beats checkout-mode readiness model.

App-local config

App-local config lives beside the app manifest as <app>/app.conf. The app manifest itself is typically authored as pharos.app.yml, with pharos.app.json emitted as the built artifact contract.

app.conf can override shared settings for one app. Common examples include:

base_path=/your-app
state_dir=/var/state/pharos
log_path=/var/log/pharos/your-app.log
error_report_path=/var/log/pharos/your-app.error.jsonl
log_level=WARN
trusted_proxy_cidrs=127.0.0.1

db_backend=sqlite
db_path=/var/lib/pharos/your-app.sqlite3
# db_socket=/var/run/postgresql
# db_charset=utf8mb4
# db_options=application_name=your-app

base_url=app.example.com
email=enabled
smtp_server=<your_primary_smtp_server>
smtp_port=587
smtp_mail_from=app_noreply@example.com
smtp_login=<your_primary_smtp_login>
smtp_pass=YOUR_APP_SMTP_PASS
smtp_encryption=STARTTLS
smtp_timeout_seconds=15
smtp_max_message_bytes=1048576
smtp2_server=<your_secondary_smtp_server>
smtp2_port=587
smtp2_mail_from=app_noreply@example.com
smtp2_login=<your_secondary_smtp_login>
smtp2_pass=YOUR_APP_SMTP_PASS
smtp2_encryption=STARTTLS
smtp2_timeout_seconds=15
smtp2_max_message_bytes=1048576

For path-like keys such as state_dir, state_path, log_path, socket_path, error_report_path, db_path, db_tls_root_cert, db_tls_cert, db_tls_key, and backup_dir, relative values resolve relative to the config file they came from.

Config resolution order

Pharos resolves effective app runtime settings in this order:

  1. App-local overrides from <app>/app.conf
  2. Shared runtime config from the file selected by PHAROS_CONFIG or passed with --conf

App-level keys override shared values only when the app config provides a real value. Missing, empty, or null app-level values fall back to the active shared runtime config.

Database contract authoring

Dynamic app source declares database.migration_contract: data/contracts/database.yml in pharos.app.yml. YAML anchors, aliases and mapping merges can share entity and field declarations. Explicit field overrides take precedence; conflicting merges, duplicate keys, tags, recursive aliases and excessive expansion fail the build.

pharos build app validates the logical contract and emits canonical data/contracts/database.json, with a JSON path in the built manifest. Runtime database loaders accept only JSON. Source-oriented migration commands use this compiler path before executing the configured generic database adapter. Contract normalization preserves logical migration checksums and existing data; changing formatting or anchor names does not create a migration. Architect generates and exports YAML sources, and imported JSON projects retain their selected authoring format.