Tonica Configuration Guide
This guide covers all configuration options available in Tonica, including environment variables, code-based configuration, and best practices.
Configuration Priority
Tonica follows this priority order (highest to lowest):
- Code - Options passed to constructors
- Environment Variables - Environment-based configuration
- Defaults - Built-in sensible defaults
Example:
// Priority 1: Code (highest)
app := tonica.NewApp(tonica.WithName("my-service"))
// Priority 2: Environment variable
// export APP_NAME="env-service"
// Priority 3: Default (lowest)
// Falls back to "tonica-app"
Application Configuration
App Options
Configure the main application:
app := tonica.NewApp(
tonica.WithName("my-service"), // Application name
tonica.WithSpec("openapi/spec.json"), // OpenAPI spec path
tonica.WithSpecUrl("/swagger.json"), // Custom spec URL
tonica.WithConfig(customConfig), // Custom config object
tonica.WithLogger(customLogger), // Custom logger
tonica.WithRegistry(customRegistry), // Custom registry
)
WithName
Sets the application name (used in metrics, logging, etc.).
tonica.WithName("user-service")
Environment Variable:
export APP_NAME="user-service"
Default: "tonica-app"
WithSpec
Sets the path to the OpenAPI specification file.
tonica.WithSpec("openapi/myservice/v1/myservice.swagger.json")
Environment Variable:
export OPENAPI_SPEC="openapi/myservice/v1/myservice.swagger.json"
Default: "" (no spec loaded)
WithSpecUrl
Sets the URL path where the OpenAPI spec will be served.
tonica.WithSpecUrl("/api-spec.json")
Default: "/openapi.json"
Server Ports
Configure HTTP, gRPC, and metrics ports:
Environment Variables:
# HTTP/REST server port
export APP_PORT="8080" # Default: 8080
# gRPC server port
export GRPC_PORT="50051" # Default: 50051
# Metrics endpoint port
export METRICS_PORT="9090" # Default: 9090
Example:
# Run on custom ports
export APP_PORT="3000"
export GRPC_PORT="9000"
export METRICS_PORT="9100"
CORS Configuration
Configure Cross-Origin Resource Sharing:
Environment Variables:
# Allow all origins (default)
# No configuration needed
# Restrict to specific origins
export APP_CORS_ORIGINS="https://myapp.com,https://admin.myapp.com"
Default: Allows all origins
Allowed Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Allowed Headers: Origin, Content-Length, Content-Type, Authorization
Service Configuration
Configure gRPC services:
svc := tonica.NewService(
tonica.WithServiceName("UserService"), // Service name
tonica.WithDB(db), // Database client
tonica.WithRedis(redis), // Redis client
)
WithServiceName
Sets the service name for registration.
tonica.WithServiceName("UserService")
Required: Yes (for registration)
WithDB
Attaches a database client to the service.
db := tonica.NewDB(...)
svc := tonica.NewService(tonica.WithDB(db))
WithRedis
Attaches a Redis client to the service.
redis := tonica.NewRedis(...)
svc := tonica.NewService(tonica.WithRedis(redis))
Database Configuration
Tonica supports PostgreSQL, MySQL, and SQLite via Bun ORM.
PostgreSQL
db := tonica.NewDB(
tonica.WithDriver(tonica.Postgres),
tonica.WithDSN("postgres://user:password@localhost:5432/dbname?sslmode=disable"),
)
Environment Variables:
export DB_DRIVER="postgres"
export DB_DSN="postgres://user:password@localhost:5432/dbname?sslmode=disable"
DSN Format:
postgres://username:password@host:port/database?sslmode=disable
Options:
sslmode:disable,require,verify-ca,verify-fullconnect_timeout: Connection timeout in secondsapplication_name: Application name for logging
Example:
export DB_DSN="postgres://myuser:mypass@db.example.com:5432/mydb?sslmode=require&connect_timeout=10"
MySQL
db := tonica.NewDB(
tonica.WithDriver(tonica.Mysql),
tonica.WithDSN("user:password@tcp(localhost:3306)/dbname?parseTime=true"),
)
Environment Variables:
export DB_DRIVER="mysql"
export DB_DSN="user:password@tcp(localhost:3306)/dbname?parseTime=true"
DSN Format:
username:password@tcp(host:port)/database?parseTime=true
Important Options:
parseTime=true: Required for proper time handlingcharset=utf8mb4: Character set (recommended)loc=Local: Timezone location
Example:
export DB_DSN="myuser:mypass@tcp(mysql.example.com:3306)/mydb?parseTime=true&charset=utf8mb4"
SQLite
db := tonica.NewDB(
tonica.WithDriver(tonica.Sqlite),
tonica.WithDSN("file:./data/mydb.db?cache=shared&mode=rwc"),
)
Environment Variables:
export DB_DRIVER="sqlite"
export DB_DSN="file:./data/mydb.db?cache=shared&mode=rwc"
DSN Format:
file:path/to/database.db?cache=shared&mode=rwc
Options:
cache:shared(multi-connection) orprivatemode:ro(read-only),rw(read-write),rwc(read-write-create)
Example:
export DB_DSN="file:/var/lib/myapp/data.db?cache=shared&mode=rwc"
Database Options
WithDriver
Sets the database driver.
tonica.WithDriver(tonica.Postgres) // PostgreSQL
tonica.WithDriver(tonica.Mysql) // MySQL
tonica.WithDriver(tonica.Sqlite) // SQLite
Environment Variable:
export DB_DRIVER="postgres" # or "mysql", "sqlite"
Default: "postgres"
WithDSN
Sets the database connection string.
tonica.WithDSN("postgres://localhost/mydb")
Environment Variable:
export DB_DSN="postgres://localhost/mydb"
Required: Yes (if using database)
Connection Pooling
Configure connection pooling (via Bun):
db := tonica.NewDB(...)
client := db.GetClient()
// Configure pool
client.SetMaxOpenConns(25) // Max open connections
client.SetMaxIdleConns(10) // Max idle connections
client.SetConnMaxLifetime(5 * time.Minute) // Connection lifetime
client.SetConnMaxIdleTime(10 * time.Minute) // Max idle time
Recommended Settings:
For API services (high concurrency):
client.SetMaxOpenConns(100)
client.SetMaxIdleConns(25)
client.SetConnMaxLifetime(5 * time.Minute)
For workers (low concurrency):
client.SetMaxOpenConns(10)
client.SetMaxIdleConns(5)
client.SetConnMaxLifetime(10 * time.Minute)
Redis Configuration
Configure Redis connection:
redis := tonica.NewRedis(
tonica.WithRedisAddr("localhost:6379"), // Redis address
tonica.WithRedisPassword("secret"), // Password (optional)
tonica.WithRedisDB(0), // Database number
)
Redis Options
WithRedisAddr
Sets the Redis server address.
tonica.WithRedisAddr("redis.example.com:6379")
Environment Variable:
export REDIS_ADDR="redis.example.com:6379"
Default: "localhost:6379"
WithRedisPassword
Sets the Redis password (if required).
tonica.WithRedisPassword("my-secret-password")
Environment Variable:
export REDIS_PASSWORD="my-secret-password"
Default: "" (no password)
WithRedisDB
Sets the Redis database number (0-15).
tonica.WithRedisDB(2)
Environment Variable:
export REDIS_DB="2"
Default: 0
Redis Connection Pooling
Configure connection pool (via go-redis):
client := redis.GetClient()
// Configure pool options
client.Options().PoolSize = 10 // Pool size
client.Options().MinIdleConns = 5 // Min idle connections
client.Options().MaxConnAge = 0 // Max connection age (0 = no limit)
client.Options().PoolTimeout = 4 * time.Second
client.Options().IdleTimeout = 5 * time.Minute
Temporal Configuration
Configure Temporal workers:
worker := tonica.NewWorker(
tonica.WithWorkerName("email-worker"), // Worker name
tonica.WithTaskQueue("email-tasks"), // Task queue
tonica.WithTemporalHost("temporal.example.com:7233"), // Temporal host
tonica.WithTemporalNamespace("production"), // Namespace
tonica.WithMaxConcurrentActivities(10), // Concurrency
)
Temporal Options
WithWorkerName
Sets the worker name.
tonica.WithWorkerName("report-generator")
Required: Yes
WithTaskQueue
Sets the Temporal task queue name.
tonica.WithTaskQueue("report-tasks")
Required: Yes
WithTemporalHost
Sets the Temporal server address.
tonica.WithTemporalHost("temporal.example.com:7233")
Environment Variable:
export TEMPORAL_HOST="temporal.example.com:7233"
Default: "localhost:7233"
WithTemporalNamespace
Sets the Temporal namespace.
tonica.WithTemporalNamespace("production")
Environment Variable:
export TEMPORAL_NAMESPACE="production"
Default: "default"
WithMaxConcurrentActivities
Sets max concurrent activity executions.
tonica.WithMaxConcurrentActivities(20)
Default: 10
Recommendations:
- I/O-bound activities (emails, API calls): 20-100
- CPU-bound activities (image processing, reports): 2-5
- Memory-intensive activities: 1-3
Consumer Configuration
Configure message consumers:
consumer := tonica.NewConsumer(
tonica.WithConsumerName("order-processor"), // Consumer name
tonica.WithTopic("orders"), // Topic name
tonica.WithConsumerGroup("order-handlers"), // Consumer group
tonica.WithPubSubClient(client), // PubSub client
tonica.WithHandler(handleOrder), // Message handler
)
Consumer Options
WithConsumerName
Sets the consumer name.
tonica.WithConsumerName("payment-processor")
Required: Yes
WithTopic
Sets the topic to consume from.
tonica.WithTopic("payments")
Required: Yes
WithConsumerGroup
Sets the consumer group name.
tonica.WithConsumerGroup("payment-handlers")
Default: "" (no consumer group)
WithPubSubClient
Sets the PubSub/Kafka client.
tonica.WithPubSubClient(pubsubClient)
Required: Yes
WithHandler
Sets the message handler function.
tonica.WithHandler(func(ctx context.Context, msg *pubsub.Message) error {
// Process message
return nil
})
Required: Yes
Observability Configuration
OpenTelemetry
Configure distributed tracing:
Environment Variables:
# Enable/disable tracing
export OTEL_ENABLED="true" # Default: false
# OTLP endpoint
export OTEL_ENDPOINT="localhost:4317"
# Service name (overrides APP_NAME)
export OTEL_SERVICE_NAME="user-service"
# Sampling rate (0.0 to 1.0)
export OTEL_TRACE_SAMPLING="1.0" # 100% sampling
Example (Jaeger):
export OTEL_ENABLED="true"
export OTEL_ENDPOINT="jaeger:4317"
export OTEL_SERVICE_NAME="user-service"
Example (Honeycomb):
export OTEL_ENABLED="true"
export OTEL_ENDPOINT="api.honeycomb.io:443"
export OTEL_SERVICE_NAME="user-service"
export OTEL_HEADERS="x-honeycomb-team=YOUR_API_KEY"
Logging
Configure structured logging:
Log Levels:
# Set log level
export LOG_LEVEL="debug" # debug, info, warn, error
Log Format:
# JSON format (for production)
export LOG_FORMAT="json"
# Text format (for development)
export LOG_FORMAT="text"
Example:
# Development
export LOG_LEVEL="debug"
export LOG_FORMAT="text"
# Production
export LOG_LEVEL="info"
export LOG_FORMAT="json"
Metrics
Metrics are always enabled on port 9090 by default.
Customize port:
export METRICS_PORT="9100"
Disable metrics:
// Not recommended - metrics are lightweight
// To disable, don't expose port 9090 externally
Complete Configuration Examples
Development Environment
# Application
export APP_NAME="myservice-dev"
export APP_PORT="8080"
export GRPC_PORT="50051"
export METRICS_PORT="9090"
# Database
export DB_DRIVER="sqlite"
export DB_DSN="file:./dev.db?cache=shared&mode=rwc"
# Redis
export REDIS_ADDR="localhost:6379"
export REDIS_PASSWORD=""
export REDIS_DB="0"
# Temporal
export TEMPORAL_HOST="localhost:7233"
export TEMPORAL_NAMESPACE="default"
# Logging
export LOG_LEVEL="debug"
export LOG_FORMAT="text"
# Tracing (disabled)
export OTEL_ENABLED="false"
Production Environment
# Application
export APP_NAME="myservice"
export APP_PORT="8080"
export GRPC_PORT="50051"
export METRICS_PORT="9090"
# Database
export DB_DRIVER="postgres"
export DB_DSN="postgres://user:pass@postgres.prod:5432/mydb?sslmode=require"
# Redis
export REDIS_ADDR="redis.prod:6379"
export REDIS_PASSWORD="${REDIS_SECRET}"
export REDIS_DB="0"
# Temporal
export TEMPORAL_HOST="temporal.prod:7233"
export TEMPORAL_NAMESPACE="production"
# CORS
export APP_CORS_ORIGINS="https://myapp.com,https://admin.myapp.com"
# Logging
export LOG_LEVEL="info"
export LOG_FORMAT="json"
# Tracing
export OTEL_ENABLED="true"
export OTEL_ENDPOINT="tempo.prod:4317"
export OTEL_SERVICE_NAME="myservice"
export OTEL_TRACE_SAMPLING="0.1" # 10% sampling
Docker Compose
version: '3.8'
services:
app:
image: myservice:latest
environment:
# Application
APP_NAME: myservice
APP_PORT: 8080
GRPC_PORT: 50051
METRICS_PORT: 9090
# Database
DB_DRIVER: postgres
DB_DSN: postgres://myuser:mypass@postgres:5432/mydb?sslmode=disable
# Redis
REDIS_ADDR: redis:6379
REDIS_PASSWORD: ""
REDIS_DB: 0
# Temporal
TEMPORAL_HOST: temporal:7233
TEMPORAL_NAMESPACE: default
# Observability
LOG_LEVEL: info
LOG_FORMAT: json
OTEL_ENABLED: "true"
OTEL_ENDPOINT: jaeger:4317
ports:
- "8080:8080" # HTTP
- "50051:50051" # gRPC
- "9090:9090" # Metrics
depends_on:
- postgres
- redis
- temporal
postgres:
image: postgres:15
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
temporal:
image: temporalio/auto-setup:latest
volumes:
postgres_data:
Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: myservice-config
data:
APP_NAME: "myservice"
APP_PORT: "8080"
GRPC_PORT: "50051"
METRICS_PORT: "9090"
DB_DRIVER: "postgres"
REDIS_DB: "0"
TEMPORAL_NAMESPACE: "production"
LOG_LEVEL: "info"
LOG_FORMAT: "json"
OTEL_ENABLED: "true"
OTEL_TRACE_SAMPLING: "0.1"
---
apiVersion: v1
kind: Secret
metadata:
name: myservice-secrets
type: Opaque
stringData:
DB_DSN: "postgres://user:pass@postgres:5432/db"
REDIS_ADDR: "redis:6379"
REDIS_PASSWORD: "secret"
TEMPORAL_HOST: "temporal:7233"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: myservice:latest
envFrom:
- configMapRef:
name: myservice-config
- secretRef:
name: myservice-secrets
Configuration Best Practices
1. Never Hardcode Secrets
❌ Bad:
db := tonica.NewDB(
tonica.WithDSN("postgres://admin:password123@localhost/db"),
)
✅ Good:
dsn := os.Getenv("DB_DSN")
if dsn == "" {
log.Fatal("DB_DSN is required")
}
db := tonica.NewDB(tonica.WithDSN(dsn))
2. Validate Configuration
func validateConfig() error {
if os.Getenv("DB_DSN") == "" {
return errors.New("DB_DSN is required")
}
if os.Getenv("REDIS_ADDR") == "" {
return errors.New("REDIS_ADDR is required")
}
return nil
}
func main() {
if err := validateConfig(); err != nil {
log.Fatal(err)
}
// ...
}
3. Use Environment-Specific Files
# .env.development
APP_NAME=myservice-dev
DB_DRIVER=sqlite
DB_DSN=file:./dev.db
# .env.production
APP_NAME=myservice
DB_DRIVER=postgres
DB_DSN=postgres://...
4. Document Required Variables
Create a .env.example:
# Application
APP_NAME=myservice
APP_PORT=8080
GRPC_PORT=50051
# Database (required)
DB_DRIVER=postgres
DB_DSN=postgres://user:pass@host:5432/db
# Redis (optional)
REDIS_ADDR=localhost:6379
REDIS_PASSWORD=
REDIS_DB=0
# Temporal (required for workers)
TEMPORAL_HOST=localhost:7233
TEMPORAL_NAMESPACE=default
5. Use Defaults Wisely
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
appPort := getEnvOrDefault("APP_PORT", "8080")
grpcPort := getEnvOrDefault("GRPC_PORT", "50051")
Troubleshooting
Database Connection Issues
Error: connection refused
# Check if database is running
docker ps | grep postgres
# Test connection
psql postgres://user:pass@localhost:5432/db
Error: authentication failed
# Verify credentials
echo $DB_DSN
# Check PostgreSQL logs
docker logs postgres-container
Redis Connection Issues
Error: dial tcp: connection refused
# Check if Redis is running
docker ps | grep redis
# Test connection
redis-cli -h localhost -p 6379 ping
Port Conflicts
Error: bind: address already in use
# Find what's using the port
lsof -i :8080
# Use different port
export APP_PORT="8081"
Next Steps
- Custom Routes - Add custom HTTP routes
- Testing - Test your configuration
- Best Practices - Production configuration patterns