DocsWorking with Rahti
Environment
One git-ignored .env, written once by the scaffold. Every variable Rahti reads, what a fresh clone has to put back, and why nothing is committed in its place.
cargo rahti new writes exactly one environment file — .env, at the project root — and adds it to .gitignore. It carries this project's generated session key and cookie name, and, if you scaffolded with --db, a connection string. With --mcp it also carries the generated MCP bearer token and local host allowlist.
# =============================================================================
# DATABASE
# Read by src/db.rs at startup. A connection string is a credential, which is
# why this file is git-ignored — rahti.config.json records which backend this
# project is on, and deliberately not how to reach it.
# =============================================================================
DATABASE_URL="sqlite://./app.db?mode=rwc"
# =============================================================================
# AUTHENTICATION AND SESSIONS
# Read by rahti::auth. Which routes are private lives in src/auth.rs, not here:
# a policy is code, and only the three values below are environment.
# =============================================================================
# Session signing secret. Unique and strong per app and per environment.
# Generated for this project; regenerate per environment with:
# openssl rand -base64 32
AUTH_SECRET="EGIzdpYKSTLRAPz7Vs79GSb1CQTLXFyUr3XPTrck/1Y="
# Session cookie name. Generated per project: several apps under one parent
# domain that share a cookie name overwrite each other's sessions.
AUTH_COOKIE_NAME="dad006cf0b1aa3d9"
# Optional. Session lifetime in hours; the default is 1.
SESSION_LIFETIME_HOURS="1"
# Optional. IANA timezone for application calendar dates and local daily jobs.
# Defaults to UTC. This never changes session expiry, cache TTLs, or timestamps.
APP_TIMEZONE="America/Santo_Domingo"
# =============================================================================
# MODEL CONTEXT PROTOCOL (only when the project opted into --mcp)
# =============================================================================
RAHTI_MCP_AUTH_TOKEN="another long random value — openssl rand -base64 32"
RAHTI_MCP_ALLOWED_HOSTS="localhost,127.0.0.1"
# RAHTI_MCP_ALLOWED_ORIGINS="https://example.com"Development fallbacks let the server start without credentials. Production requirements depend on the features you enable: sessions require AUTH_SECRET, and HTTP MCP requires RAHTI_MCP_AUTH_TOKEN. Optional configuration remains optional.
What a fresh clone needs
Because the file is git-ignored, a clone arrives without it. That is deliberate, and it is survivable: cargo run works immediately in development.
| Variable | A clone with no .env | What to do |
|---|---|---|
| AUTH_SECRET | Invented per process, with a warning | Generate a fresh one. Never copy another environment's. |
| AUTH_COOKIE_NAME | Falls back to rahti_session | Carry the project's own value over. This is the one value you copy rather than invent. |
| SESSION_LIFETIME_HOURS | Defaults to 1 | Set it only if one hour is wrong for you. |
| APP_TIMEZONE | Defaults to UTC | Set the application's IANA business-calendar timezone when UTC is not correct. |
| DATABASE_URL | Startup succeeds, disconnected | Set it if the project has a database. |
| RAHTI_MCP_AUTH_TOKEN | Debug MCP remains open; release MCP returns 503 | Generate a distinct long random credential when MCP is enabled. |
| RAHTI_MCP_ALLOWED_HOSTS | Transport defaults apply | Set the deployment's public hostnames when MCP is enabled. |
| RAHTI_MCP_ALLOWED_ORIGINS | No custom browser-origin allowlist | Set full origins only for browser-based MCP clients. |
# The whole file, for a project with no database.
AUTH_SECRET="a long random value — openssl rand -base64 32"
AUTH_COOKIE_NAME="dad006cf0b1aa3d9"
APP_TIMEZONE="UTC"The cost of the development fallback is that an invented key is different on every restart and on every instance, so everyone is signed out whenever the server restarts. That is fine on your machine and fatal in production, which is why a release build refuses to start without a real one.
Every variable
The complete list. Nothing else in Rahti reads the environment.
| Variable | Read by | For |
|---|---|---|
| AUTH_SECRET | rahti::auth | The HMAC key that signs the session cookie. Required in production. |
| AUTH_COOKIE_NAME | rahti::auth | The session cookie's name. Generated per project. |
| SESSION_LIFETIME_HOURS | rahti::auth | Session lifetime in whole hours. Optional; the default is 1. |
| APP_TIMEZONE | rahti::time | IANA timezone for local dates and daily schedules. Optional; the default is UTC. |
| DATABASE_URL | src/db.rs | The connection string. Optional — when absent, startup succeeds and the database helper stays disconnected. |
| PORT | rahti::listen | Overrides server.port. The name every managed platform sets. |
| HOST | rahti::listen | Overrides server.host. |
| RAHTI_DEV | the runtime | Forces development reload on or off. |
| RAHTI_PUBLIC_DIR | the runtime | Overrides the static directory. A relative path resolves against the working directory. |
| RAHTI_TAILWIND | the build step | A Tailwind binary to use instead of the one in .rahti/bin or on PATH. |
| RAHTI_MCP_AUTH_TOKEN | the optional MCP HTTP transport | Bearer credential for /mcp/. Required for the endpoint in release builds. |
| RAHTI_MCP_ALLOWED_HOSTS | the optional MCP HTTP transport | Comma-separated hostnames accepted by the DNS-rebinding guard. |
| RAHTI_MCP_ALLOWED_ORIGINS | the optional MCP HTTP transport | Comma-separated full origins accepted for browser MCP clients. |
The application calendar
APP_TIMEZONE is an IANA name such as America/Managua or Europe/Berlin. A blank value means UTC; a misspelling stops startup. It controls local calendar dates and Scheduler::daily, while stored timestamps, session expiry, cache TTLs, rate-limit windows, and signatures remain absolute. See Application timezone.
The three auth values
AUTH_SECRET signs every session cookie, so it is a credential in exactly the way a database password is. In production a missing or placeholder value stops the server rather than falling back to something guessable: a process that kept going would have signed every session with a key that is either in somebody's git history or different on each instance, and nothing about it would look broken until it was.
openssl rand -base64 32AUTH_COOKIE_NAME is not a credential — it is the project's identity under a shared parent domain. a.example.com and b.example.com both write cookies to .example.com, so two applications sharing a cookie name overwrite each other's sessions. It is generated per project so that case is handled before anyone runs into it, which also means every environment of one project has to use the same value.
It is validated as an RFC 6265 token, because both ways a bad name fails are silent: a Cookie header is split on ; and =, so a name carrying either is written but can never be read back — every sign-in appears to succeed and nobody stays signed in.
SESSION_LIFETIME_HOURS is the only one of the three that is ordinary configuration. A whole number of hours, greater than zero. A present but unreadable value — 24h — is refused rather than defaulted: falling back to one hour would sign people out all day for no visible reason.
The policy those values serve — which routes are private, where a signed-out visitor is sent — is code, in src/auth.rs, and never environment. See Authentication.
The connection string
A connection string is a credential, which is why it lives here and never in rahti.config.json. That file is committed, and it records which backend the project is on — deliberately not how to reach it.
DATABASE_URL="sqlite://./app.db?mode=rwc"DATABASE_URL="postgres://user:password@localhost:5432/app"DATABASE_URL="mysql://user:password@localhost:3306/app"cargo rahti upgrade --db adds the line to an existing .env above whatever is already there, rather than rewriting the file — your key and cookie name are left untouched. If the project has no .env at all, it prints the line instead of inventing a file. See Database.
The MCP deployment credential
RAHTI_MCP_AUTH_TOKEN protects the optional Streamable HTTP endpoint at /mcp/. It is separate from AUTH_SECRET and from every user's session: knowing it grants the read-only MCP inspection surface, not application identity. A debug build permits it to be absent for first-run development; a release endpoint without it returns 503.
RAHTI_MCP_ALLOWED_HOSTS is a comma-separated hostname allowlist for the transport's DNS-rebinding guard. RAHTI_MCP_ALLOWED_ORIGINS is the corresponding list of full origins when a browser-based MCP client needs access. Server-side MCP clients ordinarily need only the host setting. See Remote agents and discovery.
RAHTI_MCP_AUTH_TOKEN="generate-a-distinct-long-random-secret"
RAHTI_MCP_ALLOWED_HOSTS="example.com"
# Only for browser-based MCP clients:
RAHTI_MCP_ALLOWED_ORIGINS="https://example.com"Where the server binds
PORT overrides server.port and HOST overrides server.host, so a deployment names its own address without editing a committed file. A project scaffolds with "host": "0.0.0.0", the one address that is correct both on your machine and in a container, so most deployments set only the port — and usually the platform sets it for you. See Where the server binds.
A real variable always wins
rahti::load_env() is the one .env reader in the process — both rahti::auth and the application's src/db.rs call it, and it is idempotent, so neither has to know which ran first. It never overwrites a variable the environment already carries.
That ordering is a safety property, not a convenience: a deployment that exports DATABASE_URL cannot be silently redirected by a stale file left on the same machine.
# Nothing reads a .env in production — set these in the platform's own
# environment. A real environment variable always beats the file.
AUTH_SECRET a fresh key for this environment, never the development one
AUTH_COOKIE_NAME the same value the project was scaffolded with
DATABASE_URL this environment's database
PORT usually set for you by the platform
APP_TIMEZONE the business calendar, or omit it for UTC
RAHTI_MCP_AUTH_TOKEN a distinct random credential when MCP is enabled
RAHTI_MCP_ALLOWED_HOSTS the deployment's public hostnames