Skip to main content
โšก Calmops

Deployment and Production Readiness

Deployment and Production Readiness

Introduction

Deploying applications to production requires careful planning and execution. This guide covers containerization, monitoring, logging, and best practices for production-ready Go applications.

Core Concepts

Production Readiness Checklist

  • Proper error handling and logging
  • Health checks and monitoring
  • Graceful shutdown
  • Configuration management
  • Database migrations
  • Security hardening
  • Performance optimization

Good: Containerization

Docker Setup

# โœ… GOOD: Multi-stage Dockerfile
FROM golang:1.21 AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

# Final stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /root/

COPY --from=builder /app/app .

EXPOSE 8080
CMD ["./app"]

Docker Compose

# โœ… GOOD: Docker Compose setup
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://user:password@db:5432/mydb
      - LOG_LEVEL=info
    depends_on:
      - db
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Good: Health Checks

Implementing Health Checks

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// โœ… GOOD: Health check endpoint
func handleHealth(c *gin.Context) {
	health := map[string]interface{}{
		"status": "ok",
		"timestamp": time.Now(),
	}

	c.JSON(http.StatusOK, health)
}

// โœ… GOOD: Readiness check
func handleReady(c *gin.Context) {
	// Check database connection
	if err := db.Ping(); err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"status": "not ready",
			"error": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{"status": "ready"})
}

// โœ… GOOD: Liveness check
func handleLive(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"status": "alive"})
}

// โœ… GOOD: Setup health endpoints
func setupHealthChecks(router *gin.Engine) {
	router.GET("/health", handleHealth)
	router.GET("/ready", handleReady)
	router.GET("/live", handleLive)
}

Good: Logging

Structured Logging

package main

import (
	"github.com/sirupsen/logrus"
	"os"
)

// โœ… GOOD: Structured logging
func setupLogging() *logrus.Logger {
	logger := logrus.New()

	// Set output
	logger.SetOutput(os.Stdout)

	// Set format
	logger.SetFormatter(&logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})

	// Set level
	logLevel := os.Getenv("LOG_LEVEL")
	if logLevel == "" {
		logLevel = "info"
	}

	level, _ := logrus.ParseLevel(logLevel)
	logger.SetLevel(level)

	return logger
}

// โœ… GOOD: Logging middleware
func loggingMiddleware(logger *logrus.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		logger.WithFields(logrus.Fields{
			"method": c.Request.Method,
			"path":   c.Request.URL.Path,
			"ip":     c.ClientIP(),
		}).Info("Request started")

		c.Next()

		logger.WithFields(logrus.Fields{
			"method":     c.Request.Method,
			"path":       c.Request.URL.Path,
			"status":     c.Writer.Status(),
			"latency_ms": c.GetFloat64("latency"),
		}).Info("Request completed")
	}
}

// โœ… GOOD: Structured logging in handlers
func handleRequest(c *gin.Context, logger *logrus.Logger) {
	logger.WithFields(logrus.Fields{
		"user_id": c.GetString("user_id"),
		"action":  "create_user",
	}).Info("Creating user")
}

Good: Graceful Shutdown

Implementing Graceful Shutdown

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// โœ… GOOD: Graceful shutdown
func main() {
	router := gin.Default()

	// Setup routes
	router.GET("/", handleHome)

	// Create server
	server := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	// Start server in goroutine
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			panic(err)
		}
	}()

	// Wait for interrupt signal
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	// Graceful shutdown with timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		panic(err)
	}
}

func handleHome(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "ok"})
}

Good: Configuration Management

Environment-Based Configuration

package main

import (
	"os"
	"strconv"
)

// โœ… GOOD: Configuration from environment
type Config struct {
	Port         int
	DatabaseURL  string
	LogLevel     string
	Environment  string
	JWTSecret    string
}

func loadConfig() *Config {
	return &Config{
		Port:        getEnvInt("PORT", 8080),
		DatabaseURL: getEnv("DATABASE_URL", ""),
		LogLevel:    getEnv("LOG_LEVEL", "info"),
		Environment: getEnv("ENVIRONMENT", "development"),
		JWTSecret:   getEnv("JWT_SECRET", ""),
	}
}

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 intVal, err := strconv.Atoi(value); err == nil {
			return intVal
		}
	}
	return defaultValue
}

Advanced Patterns

Database Migrations

package main

import (
	"database/sql"
	"github.com/golang-migrate/migrate/v4"
	"github.com/golang-migrate/migrate/v4/database/postgres"
	_ "github.com/golang-migrate/migrate/v4/source/file"
)

// โœ… GOOD: Run migrations
func runMigrations(db *sql.DB) error {
	driver, err := postgres.WithInstance(db, &postgres.Config{})
	if err != nil {
		return err
	}

	m, err := migrate.NewWithDatabaseInstance(
		"file://migrations",
		"postgres",
		driver,
	)
	if err != nil {
		return err
	}

	return m.Up()
}

Monitoring and Metrics

package main

import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/gin-gonic/gin"
)

// โœ… GOOD: Prometheus metrics
var (
	requestCount = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "http_requests_total",
			Help: "Total HTTP requests",
		},
		[]string{"method", "path", "status"},
	)

	requestDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "http_request_duration_seconds",
			Help: "HTTP request duration",
		},
		[]string{"method", "path"},
	)
)

func init() {
	prometheus.MustRegister(requestCount)
	prometheus.MustRegister(requestDuration)
}

// โœ… GOOD: Metrics middleware
func metricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()

		c.Next()

		duration := time.Since(start).Seconds()
		requestCount.WithLabelValues(
			c.Request.Method,
			c.Request.URL.Path,
			strconv.Itoa(c.Writer.Status()),
		).Inc()

		requestDuration.WithLabelValues(
			c.Request.Method,
			c.Request.URL.Path,
		).Observe(duration)
	}
}

// โœ… GOOD: Setup metrics endpoint
func setupMetrics(router *gin.Engine) {
	router.GET("/metrics", gin.WrapH(promhttp.Handler()))
}

Best Practices

1. Use Environment Variables

// โœ… GOOD: Configuration from environment
databaseURL := os.Getenv("DATABASE_URL")

// โŒ BAD: Hardcoded configuration
databaseURL := "postgres://user:password@localhost/db"

2. Implement Health Checks

// โœ… GOOD: Health check endpoint
router.GET("/health", handleHealth)

// โŒ BAD: No health checks

3. Use Structured Logging

// โœ… GOOD: Structured logging
logger.WithFields(logrus.Fields{
	"user_id": userID,
	"action":  "login",
}).Info("User logged in")

// โŒ BAD: Unstructured logging
log.Printf("User %s logged in", userID)

4. Graceful Shutdown

// โœ… GOOD: Graceful shutdown
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
server.Shutdown(ctx)

// โŒ BAD: Immediate shutdown
os.Exit(0)

Resources

Summary

Production-ready applications require proper logging, monitoring, health checks, and graceful shutdown. Use containerization for consistency, environment variables for configuration, and structured logging for observability. Following these practices ensures reliable, maintainable production systems.

Comments