Deploying TMI Server - ericfitz/tmi GitHub Wiki
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
TMI_DATABASE_URL(PostgreSQL, MySQL, SQLServer, SQLite, Oracle) - Redis integration for caching and session management
| Method | Best For | Section |
|---|---|---|
| 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-server-container # TMI server only
make build-db # PostgreSQL only
make build-redis-container # Redis only
# Build all containers
make build-containers
# Run locally
docker run -p 8080:8080 \
-e TMI_DATABASE_URL="postgres://tmi_user:[email protected]: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 image (Dockerfile.server-oracle; for local development, make dev-up DB=oracle).
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 nginxThe TMI server can terminate TLS itself. Configure it with:
| Variable | Description |
|---|---|
TMI_SERVER_TLS_ENABLED |
Serve HTTPS directly (default: false) |
TMI_SERVER_TLS_CERT_FILE |
Path to the certificate file |
TMI_SERVER_TLS_KEY_FILE |
Path to the private key file |
TMI_SERVER_TLS_SUBJECT_NAME |
Certificate subject name (defaults to the hostname) |
TMI_SERVER_HTTP_TO_HTTPS_REDIRECT |
Redirect HTTP requests to HTTPS (default: true) |
When a reverse proxy, load balancer, or ingress terminates TLS in front of the server (nginx above, or the ALB on AWS), these settings are not needed. On AWS, the ALB terminates public TLS with an ACM certificate and, in the provided aws-public configuration, re-encrypts to the pod, which serves TLS with a mounted certificate and has TMI_SERVER_HTTP_TO_HTTPS_REDIRECT=false because the ALB already redirects HTTP to HTTPS. See Terraform-Deployment.
Use Kubernetes for large-scale, cloud-native deployments that require horizontal scaling and automated orchestration.
# namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: tmi-platform# configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: tmi-config
namespace: tmi-platform
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-platform
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-platform
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-platform
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-platform
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-platform
kubectl logs -f deployment/tmi-server -n tmi-platform
# Scale deployment
kubectl scale deployment/tmi-server --replicas=5 -n tmi-platformAll 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 |
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 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: "[email protected]"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"# 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.
- 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.