-
Notifications
You must be signed in to change notification settings - Fork 2
Deploying TMI Server
This guide covers all supported methods for deploying the TMI API server, from managed platforms to bare-metal installations.
The TMI server is a Go-based HTTP server that provides:
- RESTful API with OpenAPI 3.0 specification
- WebSocket support for real-time collaborative editing
- OAuth and SAML authentication with JWT tokens
- Role-based access control
- Multi-database support via DATABASE_URL (PostgreSQL, MySQL, SQLServer, SQLite, Oracle)
- Redis integration for caching and session management
| Method | Best For | Section |
|---|---|---|
| Heroku | Fastest path to production, managed infrastructure | Heroku Deployment |
| Docker Compose | Containerized, portable, small production or development | Docker Compose |
| Traditional Server | Full control on VPS or bare metal | Traditional Server |
| Kubernetes | Cloud-native, horizontally scalable | Kubernetes |
- Go 1.26.1 or later (verified in go.mod)
- Git
- Make
# Clone the repository
git clone https://github.com/ericfitz/tmi.git
cd tmi
# Build the server
make build-server
# The binary is written to ./bin/tmiserverThe production build injects version metadata from the .version file and the git commit hash via linker flags. Use the Makefile target for correct versioning:
# Build with version metadata (recommended)
make build-server
# Manual build with optimizations (no version metadata)
CGO_ENABLED=0 go build -ldflags="-w -s" -trimpath -o tmiserver ./cmd/server
# Cross-compile for different platforms
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -trimpath -o tmiserver-linux-amd64 ./cmd/server
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -trimpath -o tmiserver-windows-amd64.exe ./cmd/serverNote: The build target is ./cmd/server (a package path), not cmd/server/main.go. Setting CGO_ENABLED=0 produces a static binary but excludes Oracle database support.
TMI uses Chainguard container images for enhanced security with a minimal attack surface.
The Dockerfile.server in the repository uses a multi-stage build with version injection from the .version file:
# Dockerfile.server (Chainguard-based secure build)
# Builder stage: Chainguard Go image
FROM cgr.dev/chainguard/go:latest AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
# Static binary build with version metadata from .version file
# (Excludes Oracle support which requires CGO)
RUN MAJOR=$(cat .version | grep '"major"' | grep -o '[0-9]*') && \
MINOR=$(cat .version | grep '"minor"' | grep -o '[0-9]*') && \
PATCH=$(cat .version | grep '"patch"' | grep -o '[0-9]*') && \
PRERELEASE=$(cat .version | grep '"prerelease"' | sed 's/.*: *"\(.*\)".*/\1/') && \
CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X github.com/ericfitz/tmi/api.VersionMajor=${MAJOR} \
-X github.com/ericfitz/tmi/api.VersionMinor=${MINOR} \
-X github.com/ericfitz/tmi/api.VersionPatch=${PATCH} \
-X github.com/ericfitz/tmi/api.VersionPreRelease=${PRERELEASE} \
-X github.com/ericfitz/tmi/api.GitCommit=$(git rev-parse --short HEAD 2>/dev/null || echo 'development') \
-X github.com/ericfitz/tmi/api.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-trimpath -buildmode=exe -o tmiserver ./cmd/server
# Runtime stage: Chainguard static image (minimal attack surface)
FROM cgr.dev/chainguard/static:latest
COPY --from=builder /app/tmiserver /tmiserver
# No migration files needed -- schema is managed by GORM AutoMigrate at startup
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/tmiserver"]# Build individual containers (faster for iterative development)
make build-container-tmi # TMI server only
make build-container-db # PostgreSQL only
make build-container-redis # Redis only
# Build all containers
make build-containers
# Run locally
docker run -p 8080:8080 \
-e TMI_DATABASE_URL="postgres://tmi_user:password@host.docker.internal:5432/tmi?sslmode=disable" \
-e TMI_REDIS_HOST=host.docker.internal \
-e TMI_JWT_SECRET=your-secret \
tmi/tmi-server:latestNote: Container builds use CGO_ENABLED=0 for static binaries, which excludes Oracle database support. Supported databases are PostgreSQL, MySQL, SQL Server, and SQLite. For Oracle support, use the dedicated Oracle container target (make build-container-oracle).
Heroku provides the fastest path to production with managed PostgreSQL and Redis.
# Create Heroku app
heroku create my-tmi-server
# Provision addons
heroku addons:create heroku-postgresql:essential-0
heroku addons:create heroku-redis:mini
# Deploy
git push heroku mainTMI includes a setup script to automate Heroku configuration:
# Interactive configuration
make setup-heroku
# Or run directly
uv run scripts/setup-heroku-env.pyThe script automatically:
- Detects PostgreSQL and Redis credentials
- Generates secure JWT secret
- Configures OAuth callback URLs
- Sets up WebSocket CORS origins
- Prompts for OAuth provider credentials
If you prefer manual setup:
# Get database credentials (returns a postgres:// URL)
heroku pg:credentials:url DATABASE --app my-tmi-server
# Set environment variables
# TMI uses DATABASE_URL format (12-factor app pattern)
heroku config:set \
TMI_DATABASE_URL="postgres://<user>:<password>@<host>:5432/<database>?sslmode=require" \
TMI_JWT_SECRET=$(openssl rand -base64 32) \
TMI_REDIS_HOST=<redis-host> \
TMI_OAUTH_CALLBACK_URL="https://my-tmi-server.herokuapp.com/oauth2/callback" \
--app my-tmi-server
# Configure OAuth (example with Google)
# OAuth providers use the OAUTH_PROVIDERS_ prefix (not TMI_ prefixed)
heroku config:set \
OAUTH_PROVIDERS_GOOGLE_ENABLED=true \
OAUTH_PROVIDERS_GOOGLE_CLIENT_ID=your-client-id \
OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET=your-client-secret \
OAUTH_PROVIDERS_GOOGLE_NAME=Google \
OAUTH_PROVIDERS_GOOGLE_AUTHORIZATION_URL="https://accounts.google.com/o/oauth2/auth" \
OAUTH_PROVIDERS_GOOGLE_TOKEN_URL="https://oauth2.googleapis.com/token" \
OAUTH_PROVIDERS_GOOGLE_USERINFO_URL="https://www.googleapis.com/oauth2/v3/userinfo" \
OAUTH_PROVIDERS_GOOGLE_SCOPES="openid,profile,email" \
--app my-tmi-serverNote: OAuth providers are dynamically discovered from environment variables matching the pattern OAUTH_PROVIDERS_<PROVIDER>_ENABLED=true. The provider name is derived from the environment variable prefix (e.g., OAUTH_PROVIDERS_GOOGLE_* creates a provider with key google).
Critical: Heroku requires specific configuration for WebSocket support.
-
Port Binding -- The
Procfilebinds to Heroku's dynamically assigned port:
web: SERVER_PORT=$PORT bin/server
The server reads PORT from the environment when TMI_SERVER_PORT is not set (Heroku compatibility).
- CORS Origins -- Configure allowed origins for both REST API and WebSocket connections:
# Primary CORS configuration (controls API and general cross-origin access)
heroku config:set TMI_CORS_ALLOWED_ORIGINS="https://your-frontend.com,https://www.your-frontend.com" --app my-tmi-server
# WebSocket-specific allowed origins (checked during WebSocket upgrade)
heroku config:set WEBSOCKET_ALLOWED_ORIGINS="https://your-frontend.com,https://www.your-frontend.com" --app my-tmi-server- Session Affinity (optional, for multi-dyno deployments):
heroku features:enable http-session-affinity --app my-tmi-serverSession affinity uses HTTP cookies to route reconnection attempts to the same dyno.
# Vertical scaling (dyno size)
heroku ps:resize web=standard-1x --app my-tmi-server
# Horizontal scaling (more dynos)
heroku ps:scale web=2 --app my-tmi-server# View logs
heroku logs --tail --app my-tmi-server
# Check dyno status
heroku ps --app my-tmi-server
# Database info
heroku pg:info --app my-tmi-server
# Redis info
heroku redis:info --app my-tmi-serverTMI uses the following files for Heroku deployment:
-
Procfile --
web: SERVER_PORT=$PORT bin/server. Heroku's Go buildpack names the binaryserverfrom the last path component ofGO_INSTALL_PACKAGE_SPEC. -
app.json -- Defines addons (PostgreSQL essential-0, Redis mini), environment variables,
GO_INSTALL_PACKAGE_SPEC(github.com/ericfitz/tmi/cmd/server), andGOVERSION.
Database migrations run automatically on startup via GORM AutoMigrate. No manual migration step is needed.
Application Error (H10) -- Server failed to bind to $PORT:
# Verify Procfile uses SERVER_PORT=$PORT
cat ProcfileWebSocket CORS Error -- Add your frontend origin to the allowed origins:
heroku config:set TMI_CORS_ALLOWED_ORIGINS="https://your-frontend.com" --app my-tmi-server
heroku config:set WEBSOCKET_ALLOWED_ORIGINS="https://your-frontend.com" --app my-tmi-serverFor detailed Heroku deployment documentation including cost estimation, security considerations, and CI/CD integration, see the Heroku Deployment Guide.
Docker Compose is best suited for development and small production deployments.
Create docker-compose.yml:
version: "3.8"
services:
tmi:
image: tmi-server:latest
ports:
- "8080:8080"
environment:
- TMI_DATABASE_URL=postgres://tmi_user:${POSTGRES_PASSWORD}@postgres:5432/tmi?sslmode=disable
- TMI_REDIS_HOST=redis
- TMI_REDIS_PORT=6379
- TMI_REDIS_PASSWORD=${REDIS_PASSWORD}
- TMI_JWT_SECRET=${JWT_SECRET}
- TMI_OAUTH_CALLBACK_URL=https://tmi.example.com/oauth2/callback
- TMI_CORS_ALLOWED_ORIGINS=https://tmi.example.com
- TMI_LOG_LEVEL=info
- TMI_LOG_IS_DEV=false
# OAuth providers use non-TMI-prefixed env vars with dynamic discovery
- OAUTH_PROVIDERS_GOOGLE_ENABLED=true
- OAUTH_PROVIDERS_GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- OAUTH_PROVIDERS_GOOGLE_NAME=Google
- OAUTH_PROVIDERS_GOOGLE_AUTHORIZATION_URL=https://accounts.google.com/o/oauth2/auth
- OAUTH_PROVIDERS_GOOGLE_TOKEN_URL=https://oauth2.googleapis.com/token
- OAUTH_PROVIDERS_GOOGLE_USERINFO_URL=https://www.googleapis.com/oauth2/v3/userinfo
- OAUTH_PROVIDERS_GOOGLE_SCOPES=openid,profile,email
depends_on:
- postgres
- redis
restart: unless-stopped
postgres:
image: postgres:15
environment:
- POSTGRES_USER=tmi_user
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=tmi
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
ports:
- "5432:5432"
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
restart: unless-stopped
ports:
- "6379:6379"
volumes:
postgres_data:
redis_data:POSTGRES_PASSWORD=secure_db_password
REDIS_PASSWORD=secure_redis_password
JWT_SECRET=your-very-secure-jwt-secret-key-minimum-32-characters
GOOGLE_CLIENT_ID=your-google-oauth-client-id
GOOGLE_CLIENT_SECRET=your-google-oauth-client-secretImportant: The .env file variables above are used for Docker Compose variable substitution (the ${...} references in the compose file). The TMI server itself reads TMI_-prefixed environment variables, which are set explicitly in the compose file's environment section.
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f tmi
# Stop services
docker-compose down
# Update and restart
docker-compose pull
docker-compose up -dUse this method when deploying to a VPS, bare-metal server, or existing infrastructure where you manage the operating system directly.
# Create dedicated user
sudo useradd -r -s /bin/false tmi
# Create directories
sudo mkdir -p /opt/tmi /etc/tmi /var/log/tmi
sudo chown tmi:tmi /var/log/tmiPostgreSQL:
# Ubuntu/Debian
sudo apt update
sudo apt install postgresql postgresql-contrib
# CentOS/RHEL
sudo yum install postgresql-server postgresql-contrib
sudo postgresql-setup initdbRedis:
# Ubuntu/Debian
sudo apt install redis-server
# CentOS/RHEL
sudo yum install epel-release
sudo yum install redis# Copy binary and config
sudo cp tmiserver /opt/tmi/
sudo cp config-production.yml /etc/tmi/
# Set permissions
sudo chown -R tmi:tmi /opt/tmi
sudo chmod +x /opt/tmi/tmiserverNote: No migration files need to be copied. TMI uses GORM AutoMigrate to manage the database schema automatically at startup.
Create /etc/systemd/system/tmi.service:
[Unit]
Description=TMI Threat Modeling Server
After=network.target postgresql.service redis.service
[Service]
Type=simple
User=tmi
Group=tmi
WorkingDirectory=/opt/tmi
ExecStart=/opt/tmi/tmiserver --config=/etc/tmi/config-production.yml
Restart=always
RestartSec=5
# Environment variables (use TMI_ prefix)
Environment=TMI_DATABASE_URL=postgres://tmi_user:your-db-password@localhost:5432/tmi?sslmode=disable
Environment=TMI_JWT_SECRET=your-secure-jwt-secret
Environment=TMI_REDIS_HOST=localhost
Environment=TMI_OAUTH_CALLBACK_URL=https://api.tmi.example.com/oauth2/callback
Environment=TMI_CORS_ALLOWED_ORIGINS=https://tmi.example.com
# OAuth providers (use OAUTH_PROVIDERS_ prefix, not TMI_)
Environment=OAUTH_PROVIDERS_GOOGLE_ENABLED=true
Environment=OAUTH_PROVIDERS_GOOGLE_CLIENT_ID=your-google-client-id
Environment=OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET=your-google-secret
# Security settings
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/tmi
PrivateTmp=true
[Install]
WantedBy=multi-user.target# Reload systemd
sudo systemctl daemon-reload
# Enable and start
sudo systemctl enable tmi
sudo systemctl start tmi
# Check status
sudo systemctl status tmi
# View logs
sudo journalctl -u tmi -fCreate /etc/nginx/sites-available/tmi:
server {
listen 443 ssl http2;
server_name api.tmi.example.com;
ssl_certificate /etc/ssl/certs/tmi.crt;
ssl_private_key /etc/ssl/private/tmi.key;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name api.tmi.example.com;
return 301 https://$server_name$request_uri;
}Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/tmi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxUse Kubernetes for large-scale, cloud-native deployments that require horizontal scaling and automated orchestration.
# namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: tmi# configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: tmi-config
namespace: tmi
data:
TMI_REDIS_HOST: "redis"
TMI_REDIS_PORT: "6379"
TMI_OAUTH_CALLBACK_URL: "https://tmi.example.com/oauth2/callback"
TMI_CORS_ALLOWED_ORIGINS: "https://tmi.example.com"
TMI_LOG_LEVEL: "info"
TMI_LOG_IS_DEV: "false"# secrets.yml
apiVersion: v1
kind: Secret
metadata:
name: tmi-secrets
namespace: tmi
type: Opaque
data:
TMI_DATABASE_URL: <base64-encoded-database-url>
TMI_JWT_SECRET: <base64-encoded-jwt-secret>
TMI_REDIS_PASSWORD: <base64-encoded-redis-password>
OAUTH_PROVIDERS_GOOGLE_ENABLED: <base64-encoded: dHJ1ZQ==>
OAUTH_PROVIDERS_GOOGLE_CLIENT_ID: <base64-encoded-client-id>
OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET: <base64-encoded-client-secret>Note: The TMI_DATABASE_URL value should be a full connection string, e.g., postgres://user:pass@postgres:5432/tmi?sslmode=require.
# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tmi-server
namespace: tmi
spec:
replicas: 3
selector:
matchLabels:
app: tmi-server
template:
metadata:
labels:
app: tmi-server
spec:
containers:
- name: tmi-server
image: tmi-server:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: tmi-config
- secretRef:
name: tmi-secrets
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"# service.yml
apiVersion: v1
kind: Service
metadata:
name: tmi-service
namespace: tmi
spec:
selector:
app: tmi-server
ports:
- port: 80
targetPort: 8080
type: ClusterIP# ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tmi-ingress
namespace: tmi
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/websocket-services: "tmi-service"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.tmi.example.com
secretName: tmi-tls
rules:
- host: api.tmi.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tmi-service
port:
number: 80# Apply all manifests
kubectl apply -f namespace.yml
kubectl apply -f configmap.yml
kubectl apply -f secrets.yml
kubectl apply -f deployment.yml
kubectl apply -f service.yml
kubectl apply -f ingress.yml
# Check deployment
kubectl get pods -n tmi
kubectl logs -f deployment/tmi-server -n tmi
# Scale deployment
kubectl scale deployment/tmi-server --replicas=5 -n tmiAll configuration can be set via TMI_-prefixed environment variables. OAuth provider configuration uses a separate dynamic discovery pattern (see below).
| Variable | Required | Default | Description |
|---|---|---|---|
TMI_SERVER_PORT |
No | 8080 | HTTP server port (also reads PORT for Heroku compat) |
TMI_SERVER_INTERFACE |
No | 0.0.0.0 | Interface to bind |
TMI_SERVER_BASE_URL |
No | (auto) | Public base URL for callbacks |
TMI_SERVER_TLS_ENABLED |
No | false | Enable HTTPS |
TMI_SERVER_TLS_CERT_FILE |
No | - | TLS certificate path |
TMI_SERVER_TLS_KEY_FILE |
No | - | TLS key path |
TMI_DATABASE_URL |
Yes | - | Database connection URL (e.g., postgres://user:pass@host:5432/db?sslmode=require) |
TMI_REDIS_URL |
No | - | Redis connection URL (takes precedence over individual fields) |
TMI_REDIS_HOST |
Yes* | localhost | Redis host (*required if TMI_REDIS_URL not set) |
TMI_REDIS_PORT |
No | 6379 | Redis port |
TMI_REDIS_PASSWORD |
No | - | Redis password |
TMI_REDIS_DB |
No | 0 | Redis database number |
TMI_JWT_SECRET |
Yes | - | JWT signing secret |
TMI_JWT_EXPIRATION_SECONDS |
No | 3600 | JWT token expiration (seconds) |
TMI_JWT_SIGNING_METHOD |
No | HS256 | JWT signing method |
TMI_OAUTH_CALLBACK_URL |
Yes | - | OAuth callback URL |
TMI_CORS_ALLOWED_ORIGINS |
No | - | Comma-separated allowed CORS origins |
TMI_LOG_LEVEL |
No | info | Log level (debug, info, warn, error) |
TMI_LOG_IS_DEV |
No | true | Development mode logging |
TMI_LOG_DIR |
No | logs | Directory for log files |
TMI_OPERATOR_NAME |
No | - | Operator/maintainer name |
TMI_OPERATOR_CONTACT |
No | - | Operator contact info |
TMI_SECRETS_PROVIDER |
No | env | Secret provider (env, aws, oci) |
WEBSOCKET_ALLOWED_ORIGINS |
No | - | WebSocket-specific allowed origins |
| Variable | Required | Default | Description |
|---|---|---|---|
TMI_DB_MAX_OPEN_CONNS |
No | 10 | Maximum open database connections |
TMI_DB_MAX_IDLE_CONNS |
No | 2 | Maximum idle connections |
TMI_DB_CONN_MAX_LIFETIME |
No | 240 | Max connection lifetime (seconds) |
TMI_DB_CONN_MAX_IDLE_TIME |
No | 30 | Max idle time (seconds) |
OAuth providers are configured via environment variables with the pattern OAUTH_PROVIDERS_<PROVIDER>_*. The server dynamically discovers providers by scanning for OAUTH_PROVIDERS_<PROVIDER>_ENABLED=true.
| Variable Pattern | Description |
|---|---|
OAUTH_PROVIDERS_<PROVIDER>_ENABLED |
Set to true to enable the provider |
OAUTH_PROVIDERS_<PROVIDER>_CLIENT_ID |
OAuth client ID |
OAUTH_PROVIDERS_<PROVIDER>_CLIENT_SECRET |
OAuth client secret |
OAUTH_PROVIDERS_<PROVIDER>_NAME |
Display name |
OAUTH_PROVIDERS_<PROVIDER>_AUTHORIZATION_URL |
OAuth authorization endpoint |
OAUTH_PROVIDERS_<PROVIDER>_TOKEN_URL |
OAuth token endpoint |
OAUTH_PROVIDERS_<PROVIDER>_USERINFO_URL |
User info endpoint |
OAUTH_PROVIDERS_<PROVIDER>_SCOPES |
Comma-separated scopes |
OAUTH_PROVIDERS_<PROVIDER>_ICON |
Icon identifier |
Where <PROVIDER> is an uppercase identifier like GOOGLE, GITHUB, or MICROSOFT.
For Heroku or single-administrator deployments:
| Variable | Description |
|---|---|
TMI_ADMIN_PROVIDER |
OAuth/SAML provider ID (e.g., google) |
TMI_ADMIN_PROVIDER_ID |
Provider's unique user ID |
TMI_ADMIN_EMAIL |
Admin email (optional) |
TMI_ADMIN_SUBJECT_TYPE |
user or group (default: user) |
You can also use a YAML configuration file instead of (or alongside) environment variables. The configuration structure uses a database.url field containing a connection string URL rather than individual host/port/user/password fields:
# config-production.yml
server:
port: "8080"
interface: "0.0.0.0"
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: "" # Set via TMI_DATABASE_URL environment variable; never store passwords in config files
connection_pool:
max_open_conns: 25
max_idle_conns: 25
conn_max_lifetime: 300
conn_max_idle_time: 300
redis:
host: "redis.example.com"
port: "6379"
password: "" # Set via TMI_REDIS_PASSWORD
db: 0
auth:
jwt:
secret: "" # Set via TMI_JWT_SECRET
expiration_seconds: 3600
signing_method: "HS256"
oauth:
callback_url: "https://tmi.example.com/oauth2/callback"
providers:
google:
id: "google"
name: "Google"
enabled: true
icon: "fa-brands fa-google"
client_id: "" # Set via env var
client_secret: "" # Set via env var
authorization_url: "https://accounts.google.com/o/oauth2/auth"
token_url: "https://oauth2.googleapis.com/token"
userinfo:
- url: "https://www.googleapis.com/oauth2/v3/userinfo"
claims: {}
issuer: "https://accounts.google.com"
scopes:
- openid
- profile
- email
websocket:
inactivity_timeout_seconds: 600
logging:
level: "warn"
is_dev: false
log_dir: "/var/log/tmi"
max_age_days: 30
max_size_mb: 500
operator:
name: "TMI Production Instance"
contact: "admin@yourdomain.com"A full example configuration is available at config-example.yml in the repository root.
To start the server with a config file:
./tmiserver --config=/etc/tmi/config-production.ymlYou can also generate a configuration template:
./tmiserver --generate-configTMI provides health check endpoints that do not require authentication:
# Root endpoint (returns API info with health status)
curl http://localhost:8080/
# API server info (alias for root endpoint)
curl http://localhost:8080/api/server-info
# OAuth providers listing
curl http://localhost:8080/oauth2/providersExpected response from / (root endpoint):
{
"status": {
"code": "OK",
"time": "2025-12-01T12:00:00Z"
},
"service": {
"build": "1.4.0-rc.0+abc1234",
"name": "TMI"
},
"api": {
"specification": "https://github.com/ericfitz/tmi/blob/main/api-schema/tmi-openapi.json",
"version": "1.4.0"
}
}The status.code field reflects database and Redis health: OK, DEGRADED, or UNKNOWN. When degraded, a health object is included with component-level details.
Note: There is no /version endpoint. The root endpoint (/) serves as the health check and provides version information. When accessed from a browser (Accept: text/html), it returns an HTML page; API clients receive JSON.
Check the configuration and logs:
# Start the server with a config file to see errors in the console
./tmiserver --config=config-production.yml
# Check logs if running under systemd
journalctl -u tmi -n 50Common issues:
- Missing
TMI_JWT_SECRET - Missing or invalid
TMI_DATABASE_URL - Redis not reachable (
TMI_REDIS_HOST) - Invalid OAuth credentials
Check CORS configuration:
# Verify allowed origins
echo $TMI_CORS_ALLOWED_ORIGINS
echo $WEBSOCKET_ALLOWED_ORIGINS
# WebSocket connections use ticket-based auth (not Bearer tokens).
# First, obtain a ticket via GET /ws/ticket with a valid JWT,
# then connect with the ticket as a query parameter.
wscat -c "ws://localhost:8080/ws/notifications?ticket=YOUR_TICKET"Heroku-specific: Verify that SERVER_PORT=$PORT is set in the Procfile.
# Test the PostgreSQL connection using your DATABASE_URL
psql "$TMI_DATABASE_URL" -c "SELECT 1"
# Migrations run automatically at startup via GORM AutoMigrate.
# Check the server logs for migration errors:
journalctl -u tmi -n 100 | grep -i "migrate\|schema"Connection pool settings are configured via environment variables or the YAML config:
# Environment variables
TMI_DB_MAX_OPEN_CONNS=25 # Default: 10
TMI_DB_MAX_IDLE_CONNS=10 # Default: 2
TMI_DB_CONN_MAX_LIFETIME=300 # Default: 240 (seconds)
TMI_DB_CONN_MAX_IDLE_TIME=60 # Default: 30 (seconds)# In config YAML
database:
connection_pool:
max_open_conns: 25
max_idle_conns: 10
conn_max_lifetime: 300
conn_max_idle_time: 60# Monitor Redis performance
redis-cli --latency-history
# Check memory usage
redis-cli info memoryAdjust timeouts in configuration (or via TMI_SERVER_READ_TIMEOUT, TMI_SERVER_WRITE_TIMEOUT, TMI_SERVER_IDLE_TIMEOUT):
server:
read_timeout: 10s # Default: 5s
write_timeout: 30s # Default: 10s
idle_timeout: 2m0s # Default: 60s-
Always use HTTPS in production -- Set
TMI_SERVER_TLS_ENABLED=trueor terminate TLS at a reverse proxy. -
Generate strong JWT secrets -- Use 32+ cryptographically random characters:
openssl rand -base64 32. -
Use SSL for database connections -- Include
?sslmode=requirein yourTMI_DATABASE_URL. - Restrict database and Redis network access -- Apply firewall rules to limit exposure.
- Keep OAuth secrets secure -- Store them in environment variables or an external secrets provider such as AWS Secrets Manager or OCI Vault.
- Enable rate limiting -- The server includes user-based rate limiting; also configure rate limiting at the load balancer.
-
Configure CORS origins -- Set
TMI_CORS_ALLOWED_ORIGINSexplicitly. Do not leave it empty in production. - Apply regular security updates -- Keep Go, PostgreSQL, Redis, and the operating system up to date.
- Deploying-TMI-Web-Application -- Deploy the web frontend
- Setting-Up-Authentication -- Configure OAuth and SAML providers
- Component-Integration -- Connect server and frontend components
- Post-Deployment -- Verify your deployment and complete setup tasks
- Planning-Your-Deployment -- Evaluate deployment options and prerequisites
- Database-Setup -- Database installation and configuration details
- Configuration-Reference -- Complete configuration variable reference
- Security-Best-Practices -- Hardening and operational security guidance
For comprehensive deployment documentation including troubleshooting, performance tuning, and monitoring, see the Deployment Guide in the repository.
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- 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
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Database Operations
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions