Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tutorial 09: Environment Management & Configuration ⚙️

Rullst provides a validated runtime environment shared by its subsystems. New projects use RULLST_ENV; APP_ENV remains a legacy compatibility alias.


🛠️ Step 1: Define Environment Variables in .env

cargo rullst new already writes .env with RULLST_ENV=development, a random APP_KEY and the starter’s DATABASE_URL, plus a .env.example with a placeholder key. A hand-written project adds the file itself:

RULLST_ENV=development
PORT=3000
APP_KEY=REPLACE_WITH_YOUR_32_CHAR_RANDOM_KEY
DATABASE_URL=postgres://postgres:password@localhost:5432/my_app

Replace the placeholder with at least 32 bytes of random data, for example the output of openssl rand -base64 32: rullst-auth, which encrypts session cookies with APP_KEY, refuses shorter keys and the documented placeholders. PORT overrides the port passed to Server::run.


💻 Step 2: Access Configuration in Rust

#![allow(unused)]
fn main() {
use rullst::config::RullstConfig;

fn report_environment() -> Result<(), Box<dyn std::error::Error>> {
    let environment = RullstConfig::global().environment()?;
    // The process environment first, then `./.env`, as `Server` reads it.
    let database_is_configured =
        rullst::config::project_setting("DATABASE_URL")?.is_some();

    println!(
        "Booting Rullst in {environment} mode; database configured: {database_is_configured}"
    );
    Ok(())
}
}

Do not log DATABASE_URL: it commonly embeds a username and password. Report only whether configuration is present, and redact sensitive fields in structured telemetry.

The generated server bootstrap reads .env before applying its runtime policy. Neither Server nor the Artisan commands (db:migrate, db:seed, studio) load .env into the process environment, so std::env::var sees only real process variables there; read application settings with rullst::config::project_setting, which checks the process environment and then ./.env, and behaves the same in the server and in a seeder. Standalone utilities must load a dotenv file themselves or receive exported process variables. Environment precedence is exact: the process RULLST_ENV, legacy APP_ENV, then RULLST_ENV or APP_ENV in .env, then [app].env in Rullst.toml. Server never exports .env into the process environment; RullstConfig::environment() includes the .env step once a Server has started in the process, so it reports the environment the server enforces. Before that, and in standalone utilities, it reads only the process variables and [app].env, as Environment::detect always does. Unknown values are configuration errors rather than silently becoming development. rullst-auth cookie helpers also consult .env below the process variables and skip reading it when either is set; a malformed .env yields a fixed error that never quotes file content. A malformed or mistyped Rullst.toml fails with only its line and column, so the error can be logged without exposing app_key or connection strings.


Step 3: Configure the Browser Security Baseline

Server applies the same public apply_security_baseline composition in staging and production. CORS is deny-by-omission: list exact origins without a trailing slash, and enable credentialed cross-origin requests only when the application genuinely needs them.

[security]
csrf_same_site = "Strict"
cors_allow_origins = ["https://academy.example"]
cors_allow_credentials = false
csp = "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'nonce-{NONCE}'; style-src 'self' 'nonce-{NONCE}'"
coep = "require-corp"

Wildcard, path-bearing, credential-bearing, queried or duplicate CORS origins are configuration errors, as are spellings a browser never sends: an uppercase scheme or host, or an explicit default port (:443 for HTTPS, :80 for HTTP). CORS compares the configured origin with the Origin header byte for byte. When credentials are enabled, Core still grants them only to an origin in the exact allowlist. Test the final policy behind the real TLS proxy because an intermediary can change headers and cookie behavior.

coep accepts only require-corp, credentialless, or unsafe-none. Keep the strict require-corp default for same-origin assets. If an application must load reviewed cross-origin media whose server does not emit CORP, it can explicitly select credentialless and update the relevant CSP source directive; the browser then omits credentials from those cross-origin no-CORS requests. unsafe-none lowers cross-origin isolation and should be chosen only after an application threat-model review. COEP configuration never makes an arbitrary remote origin trustworthy.

The same baseline adds Cache-Control: no-store to every response that has no Cache-Control header of its own, so pages with personal data are not stored by browsers or shared caches. Responses that should be cached, such as file downloads or calendar and RSS feeds, must set their own header; the standard /static mount already does.


💡 Key Takeaways

  • Never hardcode credentials, database passwords, or JWT secrets in .rs source code.
  • Add .env to .gitignore and commit .env.example to document required keys for team members.
  • Prefer RULLST_ENV; keep APP_ENV only while migrating an existing project.