Configuration
A Snails app has two settings files with different jobs. config/application.rb holds values and readers — the app's name and version, plus the readers for the signing key and zone — and config/environment.rb holds the settings that depend on the mode: the time zone, the logger, the job queue adapter, the mail delivery class, and the boot hooks. The split keeps a value from changing behavior just because it was declared in the wrong file.
Values live in config/application.rb#
The generated file reopens Snails::Application and declares the app's name. Add a value by adding a reader:
module Snails
class Application
def app_name
"billing"
end
def version
"1.4.2"
end
end
endRead it through Snails.app:
Snails.app.app_name # "billing"
Snails.app.version # "1.4.2"The runtime supplies these defaults when an app declares none:
| Reader | Default | Notes |
|---|---|---|
app_name |
"app" |
The server banner and container name. snails new fills this in. |
secret_key_base |
reads SECRET_KEY_BASE |
Raises when unset or empty. Override when the key comes from a secret manager. |
time_zone |
"UTC" |
Read-only; set the slot with Snails.time_zone = "Seoul" in config/environment.rb. |
version |
nil |
No default. Declare it when the app has a version; a log record omits it otherwise. |
revision |
ENV["REVISION"], then a REVISION file |
Memoized for the process. There is no git fallback — a binary may run without the repository. |
Values are read at call time. A deployment can set REVISION=... without rebuilding; a process that reads it once keeps that value.
Wiring lives in config/environment.rb#
This file runs once at boot, after the runtime and every app file. The generated file sets the time zone, selects the process queue, and registers the test mail capture:
Snails.time_zone = "UTC"
ActiveJob.queue_adapter = ActiveJob::ProcessQueue
ActionMailer::Base.delivery_method = ActionMailer::DeliveryLog if Snails.env.test?A time-zone name is resolved and validated here. "Seoul" and "Asia/Seoul" both name the same zone; an unknown name raises at boot, so a typo cannot leave the process silently in UTC. Change this line rather than setting TZ in the environment — TZ is process-global and the runtime sets it from this slot.
The logger slots, the mail provider, and any boot override belong here too. The file is app-owned; snails sync never rewrites it.
The boot chain#
main.rb is the compile root. It requires config/boot.rb, which requires the runtime boot chain, and the chain ends by requiring the app's own files:
main.rb
config/boot.rb require snails-core/boot/all
runtime the framework
config/database.rb one adapter
config/application.rb app values
config/routes.rb route table
config/codegen.rb gen/* load lists
config/environment.rb app wiringconfig/database.rb must require exactly one backend — sqlite or PostgreSQL — because the compiler keeps only the required adapter in the binary:
require_relative "../snails-core/database/sqlite"Switch adapters by changing that one line and generating the matching migration. config/codegen.rb is the app's load edges: it lists the gen/* require files, which the generator refreshes from the folders. Do not hand-maintain gen/; add the app file and let the next bin/dev or bin/test pick it up.
Snails.root is the app root as a path value. The server runs with the app root as its working directory, so it reads as "." and joins cleanly:
"#{Snails.root}/tmp" # "./tmp"
Snails.root.join("tmp", "mail").to_s # "./tmp/mail"Environment variables#
The app does not read .env. Copy .env.sample to .env, uncomment what you need, and source it before the command:
cp .env.sample .env
set -a
. ./.env
set +a| Variable | Applies to | Meaning |
|---|---|---|
SNAILS_ENV |
all | development (default), test, staging, production |
PORT |
server | Listen port; --port overrides it. Default 3000 |
WORKERS |
server | Accepted for multi-process operation; the server runs one process |
SPINEL_WORKERS |
server | Caps OS worker threads per process; the default is one per core |
SECRET_KEY_BASE |
all | Signing key read by Snails.app.secret_key_base |
REVISION |
all | Deployment identifier, ahead of a REVISION file |
DATABASE_PATH |
sqlite | Database file; relative paths resolve from the app root. Default storage/development.sqlite3 |
DATABASE_POOL_SIZE |
sqlite, PostgreSQL | Connection count; default 8 |
DATABASE_BUSY_TIMEOUT_MS |
sqlite | How long one lock wait may take; default 5000 |
PG_URL |
PostgreSQL | libpq connection string; required by the PostgreSQL adapter |
DATABASE_POOL_TIMEOUT_MS |
PostgreSQL | Checkout wait before ConnectionTimeout; default 5000 |
DATABASE_IDLE_TIMEOUT_MS |
PostgreSQL | Idle connection lifetime; default 300000 |
DATABASE_MAX_LIFETIME_MS |
PostgreSQL | Connection lifetime; default 1800000 |
WORKER_CONCURRENCY |
server | Job worker threads; default 1 |
PG_CONTAINER_NAME, PG_PORT, PG_DATABASE |
local development | Overrides for the PostgreSQL dev container |
POSTGRES_PASSWORD |
compose | Password shared by PG_URL and the database container |
PUID, PGID |
compose | Container user ids, so the ./storage mount stays yours |
SNAILS_ENV is read on first use. An out-of-vocabulary value makes every predicate false rather than raising, so a typo in the mode does not turn into a 500. Snails.env.development?, .test?, .staging?, and .production? are the predicates; Snails.env.name is the raw name. Snails.env = "test" swaps the slot in-process for a test or a console-style override.
snails doctor prints the settings it can check — the app name, the pin source, the mode, the database target, and whether the signing key is required — before the first boot.
Database connection details are covered in Concurrency. The app's Snails.time_zone slot is covered in Time, Dates, and JSON.