Deployment Guide
This comprehensive guide covers deploying Go AI SDK applications to production, including binary compilation, containerization, and cloud deployment.
Binary Compilation
Building Production Binaries
# Build for current platform
go build -o ai-app main.go
# Build with optimizations
go build -ldflags="-s -w" -o ai-app main.go
# Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build -o ai-app-linux-amd64 main.go
GOOS=darwin GOARCH=amd64 go build -o ai-app-darwin-amd64 main.go
GOOS=windows GOARCH=amd64 go build -o ai-app-windows-amd64.exe main.go
# Build with version information
VERSION=$(git describe --tags --always --dirty)
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
go build -ldflags="-X main.Version=$VERSION -X main.BuildTime=$BUILD_TIME" -o ai-app main.go
Application Structure
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"syscall"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
var (
Version = "dev"
BuildTime = "unknown"
)
type Config struct {
APIKey string
Model string
Port int
LogLevel string
Environment string
}
func loadConfig() *Config {
cfg := &Config{}
flag.StringVar(&cfg.APIKey, "api-key", os.Getenv("OPENAI_API_KEY"), "OpenAI API key")
flag.StringVar(&cfg.Model, "model", "gpt-4o-mini", "Model to use")
flag.IntVar(&cfg.Port, "port", 8080, "Port to listen on")
flag.StringVar(&cfg.LogLevel, "log-level", "info", "Log level (debug, info, warn, error)")
flag.StringVar(&cfg.Environment, "env", "production", "Environment (development, staging, production)")
flag.Parse()
if cfg.APIKey == "" {
log.Fatal("API key is required")
}
return cfg
}
func main() {
cfg := loadConfig()
log.Printf("Starting AI service v%s (built %s)", Version, BuildTime)
log.Printf("Environment: %s", cfg.Environment)
// Setup graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigCh
log.Println("Received shutdown signal...")
cancel()
}()
// Initialize provider
provider := openai.New(openai.Config{
APIKey: cfg.APIKey,
})
model, err := provider.LanguageModel(cfg.Model)
if err != nil {
log.Fatalf("Failed to initialize model: %v", err)
}
// Start application
if err := run(ctx, model, cfg); err != nil {
log.Fatalf("Application error: %v", err)
}
log.Println("Shutdown complete")
}
func run(ctx context.Context, model interface{}, cfg *Config) error {
// Your application logic here
log.Printf("Application running on port %d", cfg.Port)
<-ctx.Done()
return nil
}
Docker Deployment
Multi-Stage Dockerfile
# Build stage
FROM golang:1.22-alpine AS builder
# Install build dependencies
RUN apk add --no-cache git make
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w -X main.Version=${VERSION:-dev} -X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o ai-app \
./cmd/server
# Runtime stage
FROM alpine:3.19
# Install CA certificates for HTTPS
RUN apk --no-cache add ca-certificates
# Create non-root user
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
WORKDIR /app
# Copy binary from builder
COPY --from=builder /app/ai-app .
# Change ownership
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/app/ai-app", "health"] || exit 1
# Run application
ENTRYPOINT ["/app/ai-app"]
CMD ["serve"]
Docker Compose
version: '3.8'
services:
ai-app:
build:
context: .
dockerfile: Dockerfile
args:
VERSION: ${VERSION:-latest}
image: ai-app:${VERSION:-latest}
container_name: ai-app
restart: unless-stopped
ports:
- "8080:8080"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MODEL=gpt-4o-mini
- LOG_LEVEL=info
- ENVIRONMENT=production
env_file:
- .env.production
volumes:
- ./data:/app/data
networks:
- ai-network
healthcheck:
test: ["CMD", "/app/ai-app", "health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Optional: Redis for caching
redis:
image: redis:7-alpine
container_name: ai-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- ai-network
networks:
ai-network:
driver: bridge
volumes:
redis-data:
Build and Run
# Build image
docker build -t ai-app:latest .
# Run container
docker run -d \
--name ai-app \
-p 8080:8080 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
ai-app:latest
# Run with docker-compose
docker-compose up -d
# View logs
docker logs -f ai-app
# Stop
docker-compose down
Cloud Deployment
AWS Deployment (EC2)
#!/bin/bash
# deploy-aws.sh
# Build binary for Linux
GOOS=linux GOARCH=amd64 go build -o ai-app main.go
# Create deployment package
tar -czf deploy.tar.gz ai-app .env.production
# Upload to EC2
scp deploy.tar.gz ec2-user@your-instance:/home/ec2-user/
# SSH and deploy
ssh ec2-user@your-instance << 'EOF'
cd /home/ec2-user
tar -xzf deploy.tar.gz
# Stop existing service
sudo systemctl stop ai-app
# Replace binary
sudo mv ai-app /opt/ai-app/
sudo chmod +x /opt/ai-app/ai-app
# Start service
sudo systemctl start ai-app
sudo systemctl status ai-app
EOF
Systemd Service File
# /etc/systemd/system/ai-app.service
[Unit]
Description=AI Application Service
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/ai-app
ExecStart=/opt/ai-app/ai-app serve
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=ai-app
# Environment
Environment="OPENAI_API_KEY=your-key-here"
EnvironmentFile=/opt/ai-app/.env.production
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/ai-app/data
[Install]
WantedBy=multi-user.target
AWS ECS (Fargate)
{
"family": "ai-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "ai-app",
"image": "your-account.dkr.ecr.us-east-1.amazonaws.com/ai-app:latest",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{
"name": "ENVIRONMENT",
"value": "production"
}
],
"secrets": [
{
"name": "OPENAI_API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:account:secret:openai-key"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/ai-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "/app/ai-app health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}
Google Cloud Run
# Build and push image
gcloud builds submit --tag gcr.io/your-project/ai-app
# Deploy to Cloud Run
gcloud run deploy ai-app \
--image gcr.io/your-project/ai-app \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars "ENVIRONMENT=production" \
--set-secrets "OPENAI_API_KEY=openai-key:latest" \
--memory 512Mi \
--cpu 1 \
--min-instances 0 \
--max-instances 10 \
--concurrency 80 \
--timeout 300
Azure Container Apps
# Create resource group
az group create --name ai-app-rg --location eastus
# Create container app environment
az containerapp env create \
--name ai-app-env \
--resource-group ai-app-rg \
--location eastus
# Deploy container app
az containerapp create \
--name ai-app \
--resource-group ai-app-rg \
--environment ai-app-env \
--image your-registry.azurecr.io/ai-app:latest \
--target-port 8080 \
--ingress external \
--secrets openai-key=$OPENAI_API_KEY \
--env-vars "ENVIRONMENT=production" "OPENAI_API_KEY=secretref:openai-key" \
--cpu 0.5 \
--memory 1.0Gi \
--min-replicas 1 \
--max-replicas 5
Fly.io Deployment
# fly.toml
app = "ai-app"
primary_region = "sjc"
[build]
dockerfile = "Dockerfile"
[env]
ENVIRONMENT = "production"
MODEL = "gpt-4o-mini"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[services.concurrency]
type = "connections"
hard_limit = 25
soft_limit = 20
[[services.tcp_checks]]
interval = "15s"
timeout = "2s"
grace_period = "1s"
restart_limit = 0
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
# Deploy to Fly.io
fly launch
fly secrets set OPENAI_API_KEY=$OPENAI_API_KEY
fly deploy
fly status
fly logs
Configuration Management
Environment Variables
package config
import (
"fmt"
"os"
"strconv"
"time"
)
type Config struct {
// API Configuration
APIKey string
Model string
Provider string
// Server Configuration
Port int
ReadTimeout time.Duration
WriteTimeout time.Duration
// Application Configuration
Environment string
LogLevel string
Debug bool
// Rate Limiting
RateLimit int
RateLimitWindow time.Duration
// Caching
CacheEnabled bool
CacheTTL time.Duration
}
func Load() (*Config, error) {
cfg := &Config{
APIKey: getEnv("OPENAI_API_KEY", ""),
Model: getEnv("MODEL", "gpt-4o-mini"),
Provider: getEnv("PROVIDER", "openai"),
Port: getEnvInt("PORT", 8080),
ReadTimeout: getEnvDuration("READ_TIMEOUT", 30*time.Second),
WriteTimeout: getEnvDuration("WRITE_TIMEOUT", 30*time.Second),
Environment: getEnv("ENVIRONMENT", "production"),
LogLevel: getEnv("LOG_LEVEL", "info"),
Debug: getEnvBool("DEBUG", false),
RateLimit: getEnvInt("RATE_LIMIT", 100),
RateLimitWindow: getEnvDuration("RATE_LIMIT_WINDOW", time.Minute),
CacheEnabled: getEnvBool("CACHE_ENABLED", true),
CacheTTL: getEnvDuration("CACHE_TTL", 5*time.Minute),
}
if err := cfg.Validate(); err != nil {
return nil, err
}
return cfg, nil
}
func (c *Config) Validate() error {
if c.APIKey == "" {
return fmt.Errorf("API key is required")
}
if c.Port < 1 || c.Port > 65535 {
return fmt.Errorf("invalid port: %d", c.Port)
}
return nil
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
func getEnvBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}
}
return defaultValue
}
func getEnvDuration(key string, defaultValue time.Duration) time.Duration {
if value := os.Getenv(key); value != "" {
if duration, err := time.ParseDuration(value); err == nil {
return duration
}
}
return defaultValue
}
Monitoring and Health Checks
Health Check Endpoint
package main
import (
"context"
"encoding/json"
"net/http"
"time"
)
type HealthStatus struct {
Status string `json:"status"`
Version string `json:"version"`
Uptime string `json:"uptime"`
Checks map[string]CheckResult `json:"checks"`
}
type CheckResult struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
}
var (
// Version is set via -ldflags "-X main.Version=..." at build time,
// as shown in the "Application Structure" section above.
Version = "dev"
startTime = time.Now()
)
func healthHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
status := HealthStatus{
Status: "healthy",
Version: Version,
Uptime: time.Since(startTime).String(),
Checks: make(map[string]CheckResult),
}
// Check AI provider connection
if err := checkAIProvider(ctx); err != nil {
status.Checks["ai_provider"] = CheckResult{
Status: "unhealthy",
Message: err.Error(),
}
status.Status = "unhealthy"
} else {
status.Checks["ai_provider"] = CheckResult{Status: "healthy"}
}
// Check database (if applicable)
// if err := checkDatabase(ctx); err != nil { ... }
// Set response status code
statusCode := http.StatusOK
if status.Status != "healthy" {
statusCode = http.StatusServiceUnavailable
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(status)
}
func checkAIProvider(ctx context.Context) error {
// Test AI provider connectivity
// Return error if unhealthy
return nil
}
Production Best Practices
1. Use Environment-Specific Configuration
# .env.production
ENVIRONMENT=production
LOG_LEVEL=info
DEBUG=false
OPENAI_API_KEY=sk-...
# .env.staging
ENVIRONMENT=staging
LOG_LEVEL=debug
DEBUG=true
2. Implement Graceful Shutdown
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down gracefully...")
// Stop accepting new requests
// Complete in-flight requests
// Close connections
3. Add Structured Logging
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("request_processed",
zap.String("request_id", requestID),
zap.Int("status_code", statusCode),
zap.Duration("duration", duration),
)
4. Monitor Performance
import "github.com/prometheus/client_golang/prometheus"
var (
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "ai_request_duration_seconds",
Help: "AI request duration in seconds",
},
[]string{"model", "status"},
)
)
5. Implement Rate Limiting
import "golang.org/x/time/rate"
limiter := rate.NewLimiter(rate.Limit(100), 10)
if !limiter.Allow() {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}