-
Notifications
You must be signed in to change notification settings - Fork 1
Bootstrapping Production
This guide walks you through the steps required to bring a new TMI deployment to production for the first time. It focuses on the bootstrap configuration layer — the minimal set of settings the server reads from a YAML file and environment variables at startup — and on how the server transitions from that skeleton into a fully operational state whose settings live in the database.
The TMI configuration model has two layers: bootstrap settings (consumed at startup from config-production.yml and environment variables) and operational settings (everything else — OAuth providers, timeouts, operator info, etc. — that lives in the database and is seeded on first run). The production YAML file covers bootstrap settings only. For a full description of both layers, see Configuration-Model.
The config-production.yml shipped in the repository is a bootstrap-only skeleton. It does not hold OAuth provider definitions, email configuration, operator identity, or any other operational setting. Those values are seeded into the database on first boot and are managed thereafter via the /admin/settings API.
Use the skeleton as your starting point and fill in the values for your environment. Secret values should be supplied via a secrets provider (see Wiring Secrets) — never as inline plaintext in a file committed to version control.
server:
port: "8080"
interface: 0.0.0.0
read_timeout: 10s
write_timeout: 30s
idle_timeout: 2m0s
tls_enabled: true
tls_cert_file: "/etc/tmi/certs/server.crt"
tls_key_file: "/etc/tmi/certs/server.key"
tls_subject_name: "tmi.yourdomain.com"
http_to_https_redirect: true
database:
url: "vault://replace-me/database/url"
redis:
host: "redis.internal"
port: "6379"
password: "vault://replace-me/database/redis/password"
db: 0
auth:
build_mode: production
jwt:
secret: "vault://replace-me/auth/jwt/secret"
signing_method: "HS256"
logging:
level: warn
is_dev: false
is_test: false
log_dir: "/var/log/tmi"
max_age_days: 30
max_size_mb: 500
max_backups: 20
also_log_to_console: false
secrets:
provider: env # Set to "aws", "oci", or "vault" for production secret managementBlock summary:
| Block | Purpose |
|---|---|
server |
Network, TLS, and timeout settings. Set tls_subject_name to your actual FQDN. |
database.url |
Connection URL for the primary database. The URL scheme selects the DB engine (see Choosing the Database Backend). |
database.redis |
Redis host, port, and auth. Used for caching and session management. |
auth |
build_mode: production enables all production security checks. jwt.secret signs and verifies all tokens — rotate it carefully. |
logging |
Structured log output. In production, is_dev and is_test must both be false. |
secrets |
Selects how secret references in this file are resolved (see below). |
Operational settings are not here. If you are looking for where to configure OAuth providers, rate limits, operator name, or similar settings — those live in the database after first boot.
Set secrets.provider to the backend that holds your credentials. Supported values:
| Value | Backend |
|---|---|
env |
Environment variables (default; suitable for Heroku, Docker, simple deployments) |
aws |
AWS Secrets Manager |
azure |
Azure Key Vault |
gcp |
Google Cloud Secret Manager |
oci |
OCI Vault |
vault |
HashiCorp Vault |
Anywhere a bootstrap setting accepts a secret value, you can use one of three reference schemes instead of an inline literal:
| Scheme | Resolved from | Example |
|---|---|---|
vault://PATH |
Secrets provider (AWS, OCI, Vault, etc.) | vault://prod/tmi/jwt-secret |
env://NAME |
Environment variable | env://TMI_JWT_SECRET |
file://PATH |
File contents (whitespace-trimmed) | file:///run/secrets/jwt-secret |
Values with no scheme prefix are treated as inline literals. Inline secrets in production config files are strongly discouraged.
Examples:
# Pull the DB URL from an environment variable
database:
url: "env://DATABASE_URL"
# Pull the JWT secret from AWS Secrets Manager (when secrets.provider = "aws")
auth:
jwt:
secret: "vault://prod/tmi/auth/jwt-secret"
# Pull the Redis password from a mounted Kubernetes secret file
database:
redis:
password: "file:///run/secrets/redis-password"Secret resolution happens in three ordered phases at startup. Understanding this prevents a common bootstrap pitfall:
-
Phase 1 — Resolve the
secretsblock itself. The secrets provider has not been built yet, so onlyenv://andfile://references work here. Avault://reference inside thesecretsblock — for example, trying to supply a Vault token viavault://— is rejected with a fatal error. Supply the provider's own credentials via environment variables or mounted files. -
Phase 2 — Build the secrets provider. The now-resolved
secretsblock is used to initialise the provider (AWS client, Vault client, etc.). -
Phase 3 — Resolve remaining bootstrap secrets. Fields such as
database.url,database.redis.password, andauth.jwt.secretare resolved by dereferencingvault://references through the provider built in phase 2.
An unresolvable secret reference is fatal — the server exits rather than starting with a missing or empty secret.
At startup, TMI validates that every required bootstrap setting has a non-empty effective value (after secret resolution). If any required setting is missing, the server exits with an error that names the offending key:
FATAL required setting "database.url" is empty — set it in config-production.yml or via the TMI_DATABASE_URL environment variable
Required bootstrap settings include at minimum: database.url, auth.jwt.secret, and server.port. Correct these before the server will start.
Environment variables with the TMI_ prefix override any key in the config file. For example:
export TMI_DATABASE_URL="postgres://user:pass@db.internal:5432/tmi"
export TMI_JWT_SECRET="$(cat /run/secrets/jwt-secret)"On the first boot against an empty system_settings table, TMI seeds operational defaults from the internal classification registry. This includes defaults for OAuth provider placeholders, session timeouts, rate limits, operator metadata, and similar settings. After seeding, the database is the authoritative source for operational configuration — subsequent restarts do not re-seed.
After first boot, verify that the seeded defaults look correct:
# Retrieve a JWT (replace with your actual admin credentials or OAuth flow)
TOKEN=$(curl -s -X POST http://localhost:8080/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" | jq -r '.access_token')
# List operational settings
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/admin/settings | jq .Review the output and update any settings that do not match your environment — particularly OAuth provider configuration, operator display name, and any site-specific limits. Use the /admin/settings API to update individual settings; do not edit the YAML file for operational values.
The database engine is selected by the URL scheme in database.url:
| Scheme | Engine |
|---|---|
postgres:// |
PostgreSQL |
oracle:// |
Oracle Autonomous Database (production target) |
mysql:// |
MySQL / MariaDB |
sqlserver:// |
Microsoft SQL Server |
sqlite:// |
SQLite (development only — not supported in production) |
For Oracle ADB specifics (wallet configuration), see Platform Notes below.
The bootstrap mechanics described above are the same on every platform — what differs is how you supply the config file and where secrets come from. The subsections below give brief, platform-specific guidance and point to the dedicated deployment pages for full detail. See Deploying-TMI-Server for platform-by-platform deployment instructions.
When database.url uses the oracle:// scheme, the engine is Oracle Autonomous Database — the production target. ADB requires an Oracle wallet in addition to the connection URL; point the server at the wallet directory via the TMI_ORACLE_WALLET_LOCATION environment variable. For the complete wallet provisioning and connection setup, and for OCI-specific container configuration, see Database-Setup and OCI-Container-Deployment.
On Heroku, configuration comes entirely from config vars (environment variables) — there is no persistent config file on the dyno. Set the bootstrap keys as TMI_* config vars (for example TMI_DATABASE_URL, TMI_JWT_SECRET), which is exactly the env-override path TMI already supports. Secrets resolve via secrets.provider: env (the default) reading those config vars, or via env://NAME references. Operational config still seeds into the database on first boot as usual, so verify it via /admin/settings afterward. Destructive database reset/drop tooling exists for Heroku operators — see the Heroku operator documentation for those procedures rather than running raw SQL by hand.
In a Kubernetes deployment, mount secrets as files and reference them with file:// (for example auth.jwt.secret: file:///etc/tmi/secrets/jwt-secret), or inject them as environment variables and use env://NAME or the TMI_* overrides. The bootstrap config itself can be a ConfigMap-mounted config-production.yml containing only vault:///env:///file:// placeholders for secret values — the actual secret material comes from Kubernetes Secrets, never from the ConfigMap. For worker components (the forward-looking component platform), workers bootstrap from environment variables only — NATS URL and secret mounts — see Worker Components below.
Before TMI 1.4.0, operational settings (OAuth providers, timeouts, etc.) lived alongside bootstrap settings in the YAML config file. Starting with 1.4.0, those settings move to the database.
When the 1.4.0+ server starts and finds operational keys still present in a YAML file, it warns (but does not fail) and surfaces the drift so you know migration is incomplete. This grace period lets you migrate without an immediate outage.
Use the tmi-dbtool --import-legacy command to read operational settings from your old config file and write them into the database:
# Build the tool
make build-dbtool # PostgreSQL
make build-dbtool-oci # Oracle ADB
# Preview what will be imported (dry run — no writes)
./bin/tmi-dbtool --import-legacy \
--config config-production.yml \
-f config-legacy.yml \
--dry-run
# Import for real
./bin/tmi-dbtool --import-legacy \
--config config-production.yml \
-f config-legacy.yml| Flag | Meaning |
|---|---|
--config <file> |
Config file that provides the DB connection (database.url) |
-f / --input-file <file>
|
The legacy YAML file to import from |
--dry-run |
Preview changes without writing to the database |
--no-backup |
Skip the automatic backup step |
--no-rewrite |
Do not rewrite the input file to remove migrated keys |
After a successful import, remove the operational keys from your YAML file so the server no longer warns about them. For the complete flag reference and advanced usage, see Managing-Operational-Settings.
TMI's component platform (for K8s-style worker deployments) uses a separate, environment-variable-only bootstrap contract. A worker component reads its configuration entirely from environment variables via LoadWorker() — no YAML file, no database connection at startup. The key variables are:
| Variable | Required | Purpose |
|---|---|---|
NATS_URL |
Yes | JetStream connection URL; the worker cannot receive jobs without it |
HEARTBEAT_SUBJECT |
No | NATS subject for liveness heartbeats |
SECRET_MOUNTS |
No | Logical secret name → filesystem path of a mounted Kubernetes Secret |
LOG_LEVEL |
No | Log verbosity (default: info) |
Everything else a worker needs arrives in the job envelope or is read from a mounted secret. This section is forward-looking; consult the component and platform documentation for deployment details as that capability matures.
Before declaring a new deployment production-ready, verify each of the following:
- An admin user exists and can authenticate successfully via OAuth.
- TLS is configured:
tls_enabled: true, valid certificate and key paths set,tls_subject_namematches the public FQDN. - If the server runs behind a reverse proxy or load balancer,
server.base_url(an operational setting, set via/admin/settingsafter first boot) is set to the public-facing URL so that OAuth redirect URIs and self-referential links are correct. -
make build-server(or the equivalent OCI/container build) completed without errors and the binary reports the expected version at the root endpoint (curl https://tmi.yourdomain.com/). - The server logged no
FATAL required settingerrors at startup;ValidateRequiredpassed cleanly. - Operational defaults were seeded on first boot;
/admin/settingsreturns the expected keys. - OAuth provider configuration has been reviewed and updated in
/admin/settings(the seeded defaults are placeholders). - All secret references resolve from the secrets provider — no inline secrets appear in the config file or in environment variables that are visible in process listings.
-
logging.is_devandlogging.is_testare bothfalse;auth.build_modeisproduction. - For pre-1.4.0 migrations:
tmi-dbtool --import-legacyhas run successfully and the server no longer logs operational-key drift warnings.
- Configuration-Model — Full description of the bootstrap vs. operational setting layers
-
Managing-Operational-Settings — Runtime settings API,
tmi-dbtoolreference, and migration guide - Deploying-TMI-Server — Platform-specific deployment instructions (Heroku, Docker Compose, Kubernetes, bare-metal)
- Database-Setup — Database provisioning, migrations, and Oracle wallet configuration
- OCI-Container-Deployment — OCI / Oracle Cloud Infrastructure container deployment with Oracle ADB
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- Identity Linking
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Timmy AI Assistant
- Metadata and Extensions
- Planning Your Deployment
- Terraform Deployment (AWS, OCI, GCP, Azure)
- Deploying TMI Server
- OCI Container Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Bootstrapping Production
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Configuring Local Development
- Managing Operational Settings
- Content Extractors - Limits and Overrides
- Database Operations
- Database Security Strategies
- Transaction Isolation
- Oracle Content Feedback FK Cleanup
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Local Development Cluster
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions
- Issue Tracker Integration
- Webhook Integration
- Addon System
- MCP Integration
- Delegated Content Providers
- Setting Up Google Content Providers
- API Clients
- API Client Maintenance
- Database Tool Reference
- TMI Terraform Analyzer
- TMI Promtail Logger
- WebSocket Test Harness