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_countdefaults to the number of online CPUs, clamped between 1 and 256worker_queue_capacitydefaults tofloor(total_physical_memory / 8 MB), clamped between 32 and 8192- If physical memory cannot be detected,
worker_queue_capacityfalls 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=sqliteis 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, anddb_options - MySQL uses the same shared
db_*contract and consumesdb_socket,db_charset, anddb_optionswhen 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, anddb_options db_password_envmust be a valid environment variable name using letters, digits, and underscores, and it must not begin with a digitdb_tls_modeaccepts onlydisable,allow,prefer,require,verify-ca, orverify-fulldb_portmust be a numeric TCP port whendb_socketis not useddb_socketis consumed by PostgreSQL and MySQL socket-capable deploymentsdb_charsetis consumed by MySQL session setup and may be ignored by other backendsdb_optionsremains 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_backendvalue
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|disabledsmtp_serversmtp_portsmtp_mail_from— required when outbound email is enabledsmtp_loginsmtp_passsmtp_encryption=STARTTLS|SSLsmtp_timeout_secondssmtp_max_message_bytessmtp2_serversmtp2_portsmtp2_mail_fromsmtp2_loginsmtp2_passsmtp2_encryption=STARTTLS|SSLsmtp2_timeout_secondssmtp2_max_message_bytesbase_url
Pharos accepts only these SMTP mode pairs:
smtp_encryption=STARTTLSwithsmtp_port=587smtp_encryption=SSLwithsmtp_port=465smtp2_encryption=STARTTLSwithsmtp2_port=587smtp2_encryption=SSLwithsmtp2_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:
- App-local overrides from
<app>/app.conf - Shared runtime config from the file selected by
PHAROS_CONFIGor 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.