Chapter 4: Environment Variables#
Every piece of Tina4 configuration lives in one file. A .env at the root of your project. All optional. All with sensible defaults. Identical across Python, PHP, Ruby, and Node.js. This chapter is the complete reference.
How .env Files Work#
A .env file is plain text. Key-value pairs. Nothing more.
# This is a commentTINA4_DATABASE_URL=sqlite:///data/app.dbTINA4_DEBUG=trueโ# Blank lines are ignoredโ# Values with spaces need quotesTINA4_MAIL_FROM="My App <noreply@example.com>"โ# No quotes needed for simple valuesTINA4_SECRET=my-secret-key-change-in-productionRules#
Six rules govern the format:
- One variable per line.
- Format is
KEY=VALUE-- no spaces around the=. - Lines starting with
#are comments. - Blank lines are ignored.
- Values can be wrapped in double quotes (
"value") or single quotes ('value'). - No variable interpolation --
$OTHER_VARis treated as a literal string.
Simple format. No surprises. A developer who has never seen a .env file understands it in thirty seconds.
Every Framework Variable Needs the TINA4_ Prefix#
Tina4 3.12 renamed every framework variable to carry the TINA4_ prefix. The pre-3.12 names (SECRET, DATABASE_URL, SWAGGER_TITLE, SMTP_HOST, HOST_NAME, and the rest) no longer work. Setting one at startup does not quietly fall back to a default. The framework refuses to boot and prints each old name beside its new one, so a stale .env fails loud instead of running auth or the database on the wrong config.
Run tina4 env --migrate to rewrite an existing .env automatically, or rename by hand. During a migration window, set TINA4_ALLOW_LEGACY_ENV=true to bypass the guard while both names are present.
Runtime conventions stay un-prefixed on purpose. PORT, HOST, NODE_ENV, RACK_ENV, RUBY_ENV, and ENVIRONMENT are platform names, not Tina4 config, so the guard leaves them alone.
The .env File Is Not Committed to Git#
The .env file holds secrets. Database passwords. JWT keys. API tokens. It belongs in .gitignore. When you run tina4 init, the generated .gitignore already excludes it.
Commit a .env.example instead. Placeholder values. A map for the next developer:
# .env.example -- copy to .env and fill in real valuesTINA4_DATABASE_URL=sqlite:///data/app.dbTINA4_DEBUG=falseTINA4_SECRET=CHANGE_METINA4_MAIL_HOST=smtp.example.comTINA4_MAIL_USERNAME=TINA4_MAIL_PASSWORD=The example file documents what the application expects. The real file stays on the machine that runs it. Never in the repository.
The Priority Chain#
Tina4 resolves every configurable value through a three-level chain:
Constructor argument > .env file > Hardcoded defaultThree levels. Strict order. No exceptions.
- Constructor argument wins. Pass a value in code and it overrides everything.
- .env file is second. No code override? The
.envvalue takes over. - Default is last. Neither code nor
.envspecifies a value? The framework's built-in default applies.
This pattern holds across all variables and all four language implementations. Learn it once. Apply it everywhere.
Boolean Values#
Environment variables are strings. The .env file has no concept of true or false. Tina4 recognises these values as truthy:
| Value | Treated as |
|---|---|
true, True, TRUE | true |
1 | true |
yes, Yes, YES | true |
on, On, ON | true |
Everything else is false:
| Value | Treated as |
|---|---|
false, 0, no, off | false |
| (empty string) | false |
| (variable not set) | false |
Write whichever style your team prefers:
# All of these enable debug modeTINA4_DEBUG=trueTINA4_DEBUG=1TINA4_DEBUG=yesComplete .env Reference#
Server#
| Variable | Default | Description |
|---|---|---|
HOST | 0.0.0.0 | Bind address. 0.0.0.0 listens on all interfaces (required for Docker). 127.0.0.1 restricts to localhost. |
PORT | See below | HTTP server port. Each framework has a unique default to avoid conflicts when running side-by-side. |
TINA4_DEBUG | false | Master toggle. Enables debug overlay, full stack traces, Swagger UI, live reload, query logging. Never true in production. |
Default ports by framework:
| Framework | Default Port |
|---|---|
| PHP | 7145 |
| Python | 7146 |
| Ruby | 7147 |
| Node.js | 7148 |
Authentication#
| Variable | Default | Description |
|---|---|---|
TINA4_SECRET | tina4-default-secret | Secret key for JWT signing (HMAC-SHA256). Long, random, never committed to git. Change this in production. |
TINA4_API_KEY | (none) | Static API key for bearer token authentication. When set, requests with Authorization: Bearer {TINA4_API_KEY} are accepted. |
TINA4_TOKEN_LIMIT | 60 | Token lifetime in minutes. Tokens issued by get_token() / getToken() expire after this many minutes. |
CSRF Protection#
| Variable | Default | Description |
|---|---|---|
TINA4_CSRF | true | Enable CSRF token validation on POST/PUT/PATCH/DELETE. Set to false to disable (e.g. for internal microservices behind a firewall). |
CSRF is on by default. When enabled:
- POST/PUT/PATCH/DELETE requests must include a
formTokenin the request body or anX-Form-Tokenheader. - GET/HEAD/OPTIONS requests are not checked.
- Requests with a valid
Authorization: Bearertoken skip CSRF validation. - Routes marked
@noauth()skip CSRF validation. - Tokens in query strings are rejected (security risk).
To disable for internal services:
TINA4_CSRF=falseDatabase#
| Variable | Default | Description |
|---|---|---|
TINA4_DATABASE_URL | sqlite:///data/app.db | Connection string. The URL scheme selects the driver. |
TINA4_DATABASE_USERNAME | (from URL) | Override the username in TINA4_DATABASE_URL. Useful when credentials contain special characters. |
TINA4_DATABASE_PASSWORD | (from URL) | Override the password in TINA4_DATABASE_URL. |
TINA4_DATABASE_CHARSET | UTF8 | Firebird connection charset. Set it when a legacy database created under NONE returns double-encoded UTF-8. A ?charset= query on the connection URL takes precedence. |
TINA4_AUTOCOMMIT | true | Standalone writes auto-commit on their own connection (durable + visible across a pool); explicit transactions stay atomic. Set false for strict manual-commit mode. |
Connection string formats:
# SQLite (default -- no credentials needed)TINA4_DATABASE_URL=sqlite:///data/app.dbโ# PostgreSQLTINA4_DATABASE_URL=postgresql://user:password@hostname:5432/database_nameโ# MySQL / MariaDBTINA4_DATABASE_URL=mysql://user:password@hostname:3306/database_nameโ# Microsoft SQL ServerTINA4_DATABASE_URL=mssql://user:password@hostname:1433/database_nameโ# Firebird (charset defaults to UTF8; add ?charset=NONE for a legacy NONE database)TINA4_DATABASE_URL=firebird://user:password@hostname:3050/path/to/database.fdbโ# MongoDB (SQL queries are auto-translated)TINA4_DATABASE_URL=mongodb://user:password@hostname:27017/database_nameGotcha: Special characters in your database password -- @, #, :, / -- will break URL parsing. URL-encode them (@ becomes %40) or split the credentials into separate variables.
DB Query Cache#
| Variable | Default | Description |
|---|---|---|
TINA4_DB_CACHE | false | Enable in-memory caching of query results. |
TINA4_DB_CACHE_TTL | 30 | Cache time-to-live in seconds. |
ORM#
| Variable | Default | Description |
|---|---|---|
TINA4_ORM_PLURAL_TABLE_NAMES | false | Append "s" to auto-generated table names. When false (default), Product maps to product. When true, Product maps to products. |
CORS (Cross-Origin Resource Sharing)#
| Variable | Default | Description |
|---|---|---|
TINA4_CORS_ORIGINS | * | Comma-separated allowed origins. * allows all. In production, list your actual domains. |
TINA4_CORS_METHODS | GET,POST,PUT,PATCH,DELETE,OPTIONS | Comma-separated HTTP methods allowed in cross-origin requests. |
TINA4_CORS_HEADERS | Content-Type,Authorization,X-Request-ID | Comma-separated headers the client is allowed to send. |
TINA4_CORS_CREDENTIALS | true | Whether the browser sends cookies and auth headers in cross-origin requests. |
TINA4_CORS_MAX_AGE | 86400 | How long (seconds) the browser caches preflight responses. 86400 = 24 hours. |
Gotcha: TINA4_CORS_ORIGINS=* combined with TINA4_CORS_CREDENTIALS=true is invalid per the CORS spec. Tina4 handles this automatically -- when origin is *, the credentials header is not sent.
Security Headers#
| Variable | Default | Description |
|---|---|---|
TINA4_FRAME_OPTIONS | SAMEORIGIN | X-Frame-Options header. Prevents clickjacking. Options: DENY, SAMEORIGIN. |
TINA4_HSTS | (empty/off) | Strict-Transport-Security max-age in seconds. Set to 31536000 (1 year) in production with HTTPS. |
TINA4_CSP | default-src 'self' | Content-Security-Policy header. Controls which resources the browser is allowed to load. |
TINA4_REFERRER_POLICY | strict-origin-when-cross-origin | Referrer-Policy header. Controls what referrer info is sent with requests. |
TINA4_PERMISSIONS_POLICY | camera=(), microphone=(), geolocation=() | Permissions-Policy header. Disables browser features your app doesn't need. |
The X-Content-Type-Options: nosniff and X-XSS-Protection: 0 headers are always set (no env variable -- these are security best practices).
Rate Limiter#
| Variable | Default | Description |
|---|---|---|
TINA4_RATE_LIMIT | 100 | Maximum requests per window per IP address. |
TINA4_RATE_WINDOW | 60 | Window duration in seconds. Default: 100 requests per 60 seconds. |
The rate limiter adds three headers to every response:
X-RateLimit-Limit: 100X-RateLimit-Remaining: 85X-RateLimit-Reset: 60When the limit is exceeded, the server returns 429 Too Many Requests.
Sessions#
| Variable | Default | Description |
|---|---|---|
TINA4_SESSION_BACKEND | file | Session storage backend. Options: file, redis, valkey, mongo, database. |
TINA4_SESSION_PATH | data/sessions | Directory for file-based sessions. Relative to the project root. |
TINA4_SESSION_TTL | 3600 | Session expiry in seconds. Default: 1 hour. |
TINA4_SESSION_SAMESITE | Lax | SameSite cookie attribute. Options: Strict, Lax, None. |
Redis/Valkey Session Backend#
| Variable | Default | Description |
|---|---|---|
TINA4_SESSION_REDIS_HOST | 127.0.0.1 | Redis host. |
TINA4_SESSION_REDIS_PORT | 6379 | Redis port. |
TINA4_SESSION_REDIS_PASSWORD | (none) | Redis password. |
TINA4_SESSION_REDIS_DB | 0 | Redis database number. |
TINA4_SESSION_REDIS_PREFIX | tina4:session: | Key prefix for session data. |
TINA4_SESSION_VALKEY_HOST | localhost | Valkey host. |
TINA4_SESSION_VALKEY_PORT | 6379 | Valkey port. |
TINA4_SESSION_VALKEY_PASSWORD | (none) | Valkey password. |
TINA4_SESSION_VALKEY_DB | 0 | Valkey database number. |
TINA4_SESSION_VALKEY_PREFIX | tina4:session: | Key prefix for session data. |
MongoDB Session Backend#
| Variable | Default | Description |
|---|---|---|
TINA4_SESSION_MONGO_URI | (none) | Full MongoDB connection URI. Overrides host/port. |
TINA4_SESSION_MONGO_HOST | localhost | MongoDB host. |
TINA4_SESSION_MONGO_PORT | 27017 | MongoDB port. |
TINA4_SESSION_MONGO_USERNAME | (none) | MongoDB username. |
TINA4_SESSION_MONGO_PASSWORD | (none) | MongoDB password. |
TINA4_SESSION_MONGO_DB | tina4_sessions | MongoDB database name. |
TINA4_SESSION_MONGO_COLLECTION | sessions | MongoDB collection for session data. |
Queue#
| Variable | Default | Description |
|---|---|---|
TINA4_QUEUE_BACKEND | file | Queue storage backend. Options: file, rabbitmq, kafka, mongodb. |
TINA4_QUEUE_PATH | data/queue | Directory for file-based queue storage (when using file backend). |
TINA4_QUEUE_URL | (none) | Generic connection URL for queue backend. |
RabbitMQ Queue Backend#
| Variable | Default | Description |
|---|---|---|
TINA4_RABBITMQ_HOST | localhost | RabbitMQ host. |
TINA4_RABBITMQ_PORT | 5672 | RabbitMQ port. |
TINA4_RABBITMQ_USERNAME | guest | RabbitMQ username. |
TINA4_RABBITMQ_PASSWORD | guest | RabbitMQ password. |
TINA4_RABBITMQ_VHOST | / | RabbitMQ virtual host. |
Kafka Queue Backend#
| Variable | Default | Description |
|---|---|---|
TINA4_KAFKA_BROKERS | localhost:9092 | Comma-separated Kafka broker addresses. |
TINA4_KAFKA_GROUP_ID | tina4_consumer_group | Kafka consumer group ID. |
MongoDB Queue Backend#
| Variable | Default | Description |
|---|---|---|
TINA4_MONGO_URI | (none) | Full MongoDB connection URI. Overrides host/port. |
TINA4_MONGO_HOST | localhost | MongoDB host. |
TINA4_MONGO_PORT | 27017 | MongoDB port. |
TINA4_MONGO_USERNAME | (none) | MongoDB username. |
TINA4_MONGO_PASSWORD | (none) | MongoDB password. |
TINA4_MONGO_DB | tina4 | MongoDB database name. |
TINA4_MONGO_COLLECTION | tina4_queue | MongoDB collection for queue messages. |
Response Cache#
| Variable | Default | Description |
|---|---|---|
TINA4_CACHE_BACKEND | memory | Cache storage. Options: memory (in-process), file, redis, valkey, memcached, mongodb, database. Falls back to file if the configured backend is unreachable. |
TINA4_CACHE_TTL | 60 | Default cache TTL in seconds for cached routes. |
TINA4_CACHE_MAX_ENTRIES | 1000 | Maximum cached responses (memory backend). Oldest entries evicted at the limit. |
TINA4_CACHE_DIR | data/cache | Directory for file-based cache. |
TINA4_CACHE_URL | redis://localhost:6379 | Connection URL for remote cache backends (Redis, Valkey, Memcached, MongoDB). For database, falls back to TINA4_DATABASE_URL. |
TINA4_CACHE_USERNAME | (none) | Username for the cache backend. May also be embedded in TINA4_CACHE_URL. |
TINA4_CACHE_PASSWORD | (none) | Password for the cache backend. May also be embedded in TINA4_CACHE_URL (e.g. redis://:pass@host). Memcached is unauthenticated. |
Logging#
| Variable | Default | Description |
|---|---|---|
TINA4_LOG_LEVEL | ERROR | Minimum log level. Options: ALL, DEBUG, INFO, WARNING, ERROR. |
TINA4_LOG_MAX_SIZE | 10 | Maximum log file size in MB before rotation. |
TINA4_LOG_KEEP | 5 | Number of rotated log files to keep. |
Messenger (Email / SMTP)#
Tina4 supports two naming conventions for SMTP variables. The SMTP_* variables are the primary names. The TINA4_MAIL_* variables are aliases that take precedence when both are set.
| Variable | Alias | Default | Description |
|---|---|---|---|
TINA4_MAIL_HOST | TINA4_MAIL_HOST | localhost | SMTP server hostname. |
TINA4_MAIL_PORT | TINA4_MAIL_PORT | 587 | SMTP port. 587 (TLS), 465 (SSL), 25 (unencrypted). |
TINA4_MAIL_USERNAME | TINA4_MAIL_USERNAME | (none) | SMTP authentication username. |
TINA4_MAIL_PASSWORD | TINA4_MAIL_PASSWORD | (none) | SMTP authentication password. |
TINA4_MAIL_FROM | TINA4_MAIL_FROM | noreply@localhost | Default sender address. |
TINA4_MAIL_FROM_NAME | TINA4_MAIL_FROM_NAME | (none) | Sender display name. |
TINA4_MAIL_ENCRYPTION | - | tls | Connection encryption. tls, ssl, or none. |
IMAP (for reading email)#
| Variable | Default | Description |
|---|---|---|
TINA4_MAIL_IMAP_HOST | (falls back to TINA4MAILHOST) | IMAP server hostname. |
TINA4_MAIL_IMAP_PORT | 993 | IMAP port (993 = SSL). |
Localization (i18n)#
| Variable | Default | Description |
|---|---|---|
TINA4_LOCALE | en | Default locale. Determines which translation file (src/locales/{locale}.json) is loaded. |
TINA4_LOCALE_DIR | src/locales | Directory containing translation JSON files. |
Swagger / OpenAPI#
| Variable | Default | Description |
|---|---|---|
TINA4_SWAGGER_TITLE | Tina4 API | API title shown in Swagger UI. |
TINA4_SWAGGER_VERSION | 1.0.0 | API version shown in Swagger UI. |
TINA4_SWAGGER_DESCRIPTION | (none) | API description. |
File Uploads#
| Variable | Default | Description |
|---|---|---|
TINA4_MAX_UPLOAD_SIZE | 10485760 | Maximum upload size in bytes. Default: 10 MB. |
WebSocket#
| Variable | Default | Description |
|---|---|---|
TINA4_WS_PORT | 8080 | WebSocket server port (when running as separate process). |
TINA4_WS_BACKPLANE | (none) | WebSocket backplane type. Set to redis to relay broadcasts across instances. |
TINA4_WS_BACKPLANE_URL | redis://localhost:6379 | Connection URL for the WebSocket backplane. |
Services (Background Workers)#
| Variable | Default | Description |
|---|---|---|
TINA4_SERVICE_DIR | src/services | Directory for service worker scripts. |
TINA4_SERVICE_SLEEP | 5 | Seconds between service worker ticks. |
Dev Mailbox#
| Variable | Default | Description |
|---|---|---|
TINA4_MAILBOX_DIR | data/mailbox | Directory for captured dev emails. |
Minimal .env for Development#
Getting started? One line:
TINA4_DEBUG=trueEverything else uses sensible defaults:
- Binds to
0.0.0.0on the framework's default port - SQLite database at
data/app.db - File-based sessions (1 hour TTL)
- File-based queue
- In-memory response cache
- CORS allows all origins
- 100 requests per minute rate limit
- CSRF protection enabled
- Security headers active
One line. A working development environment. Add variables when you need them. Not before.
Minimal .env for Production#
TINA4_DEBUG=falseTINA4_SECRET=a-very-long-random-string-at-least-32-charactersTINA4_DATABASE_URL=postgresql://app_user:strong_password@db-host:5432/myappTINA4_CORS_ORIGINS=https://myapp.comTINA4_HSTS=31536000TINA4_MAIL_HOST=smtp.sendgrid.netTINA4_MAIL_PORT=587TINA4_MAIL_USERNAME=apikeyTINA4_MAIL_PASSWORD=SG.xxxxxTINA4_MAIL_FROM=noreply@myapp.comTen lines. A production application. Debug disabled. Real database. Signed tokens. Locked CORS. HSTS enabled. Email configured. Everything else keeps its defaults.
Docker .env#
When running in Docker, HOST must be 0.0.0.0 so the container accepts connections from outside. This is already the default, but if you override it, keep this in mind:
# Required for Docker -- do NOT set to 127.0.0.1HOST=0.0.0.0PORT=7145TINA4_DEBUG=falseFull .env Template#
Copy this to your .env.example as a starting point:
# =============================================================================# Tina4 Environment Configuration# Copy this file to .env and fill in your values# =============================================================================โ# --- Server ---HOST=0.0.0.0# PORT=7145TINA4_DEBUG=falseโ# --- Authentication ---TINA4_SECRET=CHANGE_ME# TINA4_API_KEY=TINA4_TOKEN_LIMIT=60โ# --- CSRF ---TINA4_CSRF=trueโ# --- Database ---TINA4_DATABASE_URL=sqlite:///data/app.db# TINA4_DATABASE_USERNAME=# TINA4_DATABASE_PASSWORD=# TINA4_AUTOCOMMIT=falseโ# --- DB Query Cache ---# TINA4_DB_CACHE=false# TINA4_DB_CACHE_TTL=30โ# --- ORM ---# TINA4_ORM_PLURAL_TABLE_NAMES=falseโ# --- CORS ---TINA4_CORS_ORIGINS=*TINA4_CORS_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONSTINA4_CORS_HEADERS=Content-Type,Authorization,X-Request-IDTINA4_CORS_CREDENTIALS=trueTINA4_CORS_MAX_AGE=86400โ# --- Security Headers ---# TINA4_FRAME_OPTIONS=SAMEORIGIN# TINA4_HSTS=# TINA4_CSP=default-src 'self'# TINA4_REFERRER_POLICY=strict-origin-when-cross-origin# TINA4_PERMISSIONS_POLICY=camera=(), microphone=(), geolocation=()โ# --- Rate Limiting ---TINA4_RATE_LIMIT=100TINA4_RATE_WINDOW=60โ# --- Logging ---TINA4_LOG_LEVEL=ERROR# TINA4_LOG_MAX_SIZE=10# TINA4_LOG_KEEP=5โ# --- Sessions ---TINA4_SESSION_BACKEND=file# TINA4_SESSION_PATH=data/sessionsTINA4_SESSION_TTL=3600# TINA4_SESSION_REDIS_HOST=127.0.0.1# TINA4_SESSION_REDIS_PORT=6379# TINA4_SESSION_VALKEY_HOST=localhost# TINA4_SESSION_VALKEY_PORT=6379โ# --- Queue ---TINA4_QUEUE_BACKEND=file# TINA4_RABBITMQ_HOST=localhost# TINA4_RABBITMQ_PORT=5672# TINA4_RABBITMQ_USERNAME=guest# TINA4_RABBITMQ_PASSWORD=guest# TINA4_KAFKA_BROKERS=localhost:9092# TINA4_KAFKA_GROUP_ID=tina4_consumer_group# TINA4_MONGO_HOST=localhost# TINA4_MONGO_PORT=27017# TINA4_MONGO_DB=tina4# TINA4_MONGO_COLLECTION=tina4_queueโ# --- Response Cache ---TINA4_CACHE_BACKEND=memoryTINA4_CACHE_TTL=60TINA4_CACHE_MAX_ENTRIES=1000โ# --- Email (SMTP) ---# TINA4_MAIL_HOST=smtp.example.com# TINA4_MAIL_PORT=587# TINA4_MAIL_USERNAME=# TINA4_MAIL_PASSWORD=# TINA4_MAIL_FROM=noreply@example.comโ# --- Localization ---TINA4_LOCALE=enโ# --- Swagger ---TINA4_SWAGGER_TITLE=Tina4 APITINA4_SWAGGER_VERSION=1.0.0# TINA4_SWAGGER_DESCRIPTION=โ# --- File Uploads ---# TINA4_MAX_UPLOAD_SIZE=10485760โ# --- Services ---# TINA4_SERVICE_DIR=src/services# TINA4_SERVICE_SLEEP=5Summary#
| Count | Category |
|---|---|
| 3 | Server (HOST, PORT, TINA4_DEBUG) |
| 3 | Authentication (TINA4SECRET, TINA4APIKEY, TINA4TOKEN_LIMIT) |
| 1 | CSRF (TINA4_CSRF) |
| 4 | Database (TINA4DATABASEURL, USERNAME, PASSWORD, AUTOCOMMIT) |
| 2 | DB query cache |
| 5 | CORS |
| 5 | Security headers |
| 2 | Rate limiter |
| 3 | Logging |
| 14 | Sessions (base + Redis + Valkey + MongoDB) |
| 15 | Queue (base + RabbitMQ + Kafka + MongoDB) |
| 5 | Response cache |
| 8 | Messenger (SMTP + IMAP) |
| 2 | Localization |
| 3 | Swagger |
| 1 | File uploads |
| 3 | WebSocket |
| 2 | Services |
| 1 | Dev mailbox |
| 82 | Total |
Every variable follows the same priority chain: constructor > .env > default. Every boolean is interpreted consistently across all four frameworks. Every variable has a sensible default that works for development without any configuration.
One file. Eighty-two knobs. Turn what you need. Leave the rest alone.