How to Configure PostgreSQL and Redis for Multi-Instance Grok Deployment
To run Grok2API in multi-instance mode, you must configure PostgreSQL as the persistent database, Redis as the runtime store, set unique instance IDs, and enable shared media storage, or the application will refuse to start.
When scaling the chenyme/grok2api service beyond a single replica, the default SQLite and in-memory storage backends become bottlenecks that cause data corruption and state loss. This guide explains how to configure PostgreSQL and Redis for a horizontally-scaled Grok deployment, based on the validation logic and connection handling implemented in the source code.
Prerequisites for Multi-Instance Deployment
Running multiple instances of Grok2API triggers strict validation rules in backend/internal/infra/config/config.go. The Config.Validate function (lines 80-98) enforces four hard requirements that prevent the service from starting if unmet.
Why SQLite and In-Memory Stores Fail at Scale
The single-instance mode uses SQLite for persistence and an in-memory store for runtime state. When deployment.replicas exceeds 1, these backends become unsafe:
- SQLite cannot handle concurrent writes from multiple processes, risking database corruption.
- In-memory stores lose all routing and egress queue data whenever a pod restarts.
The Four Hard Requirements
According to the validation logic in config.go, multi-instance deployments require:
- PostgreSQL driver (
database.driver: "postgres") – Provides row-level locking and durable shared storage. - Redis runtime store (
runtimeStore.driver: "redis") – Maintains shared state for routing and egress queues across replicas. - Instance and cluster identifiers –
deployment.instanceID(unique per pod) anddeployment.clusterID(shared across pods) enable tracing and leader election. - Shared media storage (
deployment.sharedMedia: true) – All replicas must access the same local volume for media files.
If any requirement is missing, the application exits with explicit errors such as "多实例部署必须使用 PostgreSQL" or "多实例部署必须使用 Redis 运行态存储".
Configuration File Structure
Create a config.yaml file that satisfies all validation rules. The configuration uses distinct sections for the persistent database and the runtime cache.
Database Configuration (PostgreSQL)
The database section must specify the PostgreSQL driver and connection details. The application uses GORM with the gorm.io/driver/postgres driver to manage connections.
database:
driver: "postgres"
postgres:
dsn: "postgres://grok_user:strong_password@postgres:5432/grok2api?sslmode=require"
maxOpenConns: 100
maxIdleConns: 20
The OpenPostgres function in backend/internal/infra/persistence/relational/database.go (lines 14-70) handles the actual connection, applying the connection pool limits and returning a configured *Database instance.
Runtime Store Configuration (Redis)
The runtimeStore section configures Redis for shared state management. This store handles routing tables, egress queues, and audit logs that must persist across pod restarts.
runtimeStore:
driver: "redis"
redis:
address: "redis:6379"
username: ""
password: "redis_secret"
database: 0
keyPrefix: "grok2api:"
tls: false
The Redis client initialization code constructs a redis.Options struct with the provided credentials and TLS configuration. The keyPrefix ensures all keys are namespaced to prevent collisions with other services.
Deployment and Media Settings
Complete the configuration with deployment identifiers and media storage:
deployment:
replicas: 3
instanceID: "instance-01" # Unique per pod (e.g., pod name in Kubernetes)
clusterID: "grok-cluster" # Shared across all replicas
sharedMedia: true
media:
driver: "local"
local:
path: "/var/grok/media"
The instanceID is critical for routing logic. As seen in the routing layer, the application constructs Redis keys using fmt.Sprintf("%s:selector:%s", cfg.RuntimeStore.Redis.KeyPrefix, cfg.Deployment.InstanceID), isolating per-instance state while maintaining access to the global pool.
Environment Variable Overrides
For Docker and Kubernetes deployments, you can override the PostgreSQL DSN using the GROK2API_DATABASE_URL environment variable instead of hardcoding credentials in the YAML file.
export GROK2API_DATABASE_URL="postgres://grok_user:strong_password@postgres:5432/grok2api?sslmode=require"
The configuration loader reads this variable (defined as DatabaseURLEnv constant) and injects the value into cfg.Database.Postgres.DSN before validation occurs. This approach keeps secrets out of version control while satisfying the multi-instance PostgreSQL requirement.
Shared Volume Setup
When deployment.sharedMedia is true, every replica must mount the same physical storage path. In Docker Compose, bind-mount a host directory:
volumes:
- ./media:/var/grok/media
In Kubernetes, use a PersistentVolumeClaim (PVC) backed by a ReadWriteMany storage class so all pods in the StatefulSet or Deployment can simultaneously access media files uploaded by users.
Code Implementation Details
Understanding the internal implementation helps troubleshoot connection failures and performance issues.
Configuration Validation (config.go)
The Validate method in backend/internal/infra/config/config.go (lines 80-98) executes the following checks sequentially:
- Lines 80-90: Verifies
database.driver == "postgres"whenreplicas > 1 - Lines 84-89: Verifies
runtimeStore.driver == "redis"for multi-instance mode - Lines 90-95: Ensures
instanceIDandclusterIDare non-empty strings - Lines 96-98: Confirms
sharedMediais enabled
Validation fails fast with descriptive errors, preventing the service from entering a partially initialized state.
PostgreSQL Connection (database.go)
The OpenPostgres function in backend/internal/infra/persistence/relational/database.go establishes the connection:
func OpenPostgres(ctx context.Context, dsn string, maxOpen, maxIdle int) (*Database, error) {
db, err := gorm.Open(postgres.Open(dsn), gormConfig())
if err != nil {
return nil, &postgresConnectionError{operation: "打开 PostgreSQL", err: err, dsn: dsn}
}
return configureDatabase(ctx, db, "postgres", maxOpen, maxIdle)
}
This function configures connection pooling according to the maxOpenConns and maxIdleConns values specified in your YAML.
Redis Client Initialization
The runtime store builder creates a Redis client with TLS support:
func newRedisStore(cfg config.RedisRuntimeConfig) (*redis.Client, error) {
opt := &redis.Options{
Addr: cfg.Address,
Username: cfg.Username,
Password: cfg.Password,
DB: cfg.Database,
TLSConfig: func() *tls.Config {
if cfg.TLS { return &tls.Config{} }
return nil
}(),
}
return redis.NewClient(opt), nil
}
The client is used throughout the application for distributed locking, rate limiting, and request routing.
Deployment Architecture
A complete multi-instance deployment requires three services:
- PostgreSQL – Official image with persistent volume for data durability
- Redis – Official image with AOF persistence enabled for runtime state recovery
- Grok2API – Built from
cmd/grok2api/main.go, mounting theconfig.yamland shared media volume
The application entry point in cmd/grok2api/main.go loads the configuration, validates the multi-instance requirements, and initializes the HTTP server only after successful database and Redis connections.
Summary
- Multi-instance mode activates when
deployment.replicas > 1, triggering strict validation inconfig.go. - PostgreSQL is mandatory for persistent storage because SQLite cannot handle concurrent access across replicas.
- Redis is mandatory for runtime storage to prevent state loss during pod restarts.
- Unique instance IDs and shared media are required for proper request routing and file access across the cluster.
- Environment variables like
GROK2API_DATABASE_URLallow secure credential injection without modifying YAML files.
Frequently Asked Questions
What happens if I try to run multiple replicas with SQLite?
The application will fail to start with the error "多实例部署必须使用 PostgreSQL". The Config.Validate function in backend/internal/infra/config/config.go explicitly checks that database.driver is set to "postgres" when replicas exceeds 1, as SQLite lacks row-level locking for concurrent processes.
Can I use a managed Redis service like AWS ElastiCache?
Yes. Configure the runtimeStore.redis section with your ElastiCache endpoint, password, and TLS settings. Set tls: true in the configuration to enable encrypted connections. The Redis client initialization code supports custom TLS configurations for managed cloud services.
How do I assign unique instance IDs in Kubernetes?
Set the deployment.instanceID value to the pod name using the Downward API. In your Kubernetes deployment manifest, inject the pod name as an environment variable and reference it in the configuration, or use a ConfigMap templating solution to ensure each pod receives a unique identifier while sharing the same clusterID.
Is shared media storage necessary if I use external object storage?
Yes. The deployment.sharedMedia requirement specifically refers to the local volume path configured in media.local.path. Even if you implement external storage adapters later, the validation logic requires sharedMedia: true to ensure the local filesystem abstraction functions correctly across replicas. Mount an NFS volume or use a ReadWriteMany PVC to satisfy this requirement.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →