Go Deployment and Production Best Practices
45 minGo applications can be compiled to single binaries for easy deployment, requiring no runtime dependencies. Go's static compilation produces self-contained executables that can run on target systems without Go installed. This simplifies deployment and reduces dependency issues. Understanding Go's deployment model helps you deploy applications efficiently.
Environment variables configure applications for different environments (development, staging, production), allowing the same binary to work across environments. Environment variables are accessed via `os.Getenv()` or configuration packages. This enables flexible, environment-specific configuration. Understanding environment configuration helps you deploy applications across environments.
Health checks and monitoring ensure application reliability by providing endpoints that report application status. Health checks verify the application is running; readiness checks verify it can serve traffic. Monitoring tracks metrics, logs, and performance. Understanding health checks and monitoring helps you maintain reliable production systems.
Containerization with Docker simplifies deployment and scaling by packaging applications and dependencies into containers. Dockerfiles define container images; containers run consistently across environments. Containerization enables easy scaling, deployment, and management. Understanding containerization helps you deploy applications reliably.
Production best practices include graceful shutdown (handling SIGTERM/SIGINT), proper logging, error handling, security (HTTPS, authentication), rate limiting, and observability (metrics, tracing). Production applications must be reliable, secure, and maintainable. Understanding production practices helps you build robust systems.
Deployment strategies include blue-green deployments, rolling updates, and canary releases. CI/CD pipelines automate building, testing, and deployment. Understanding deployment strategies helps you release updates safely and efficiently.
Key Concepts
- Go compiles to single binaries for easy deployment.
- Environment variables configure applications for different environments.
- Health checks and monitoring ensure application reliability.
- Containerization with Docker simplifies deployment and scaling.
- Production applications require proper logging, security, and monitoring.
Learning Objectives
Master
- Deploying Go applications as single binaries
- Configuring applications with environment variables
- Implementing health checks and monitoring
- Containerizing applications with Docker
Develop
- Production deployment thinking
- Understanding reliability and observability
- Building production-ready applications
Tips
- Use environment variables for configuration.
- Implement health and readiness checks.
- Use Docker for consistent deployments.
- Implement graceful shutdown for clean restarts.
Common Pitfalls
- Hardcoding configuration, making deployment inflexible.
- Not implementing health checks, making monitoring difficult.
- Not handling graceful shutdown, causing data loss.
- Not securing applications, causing security vulnerabilities.
Summary
- Go compiles to single binaries for easy deployment.
- Environment variables enable flexible configuration.
- Health checks and monitoring ensure reliability.
- Containerization simplifies deployment and scaling.
- Understanding production practices enables robust systems.
Exercise
Create a production-ready Go application with Docker, environment configuration, and health checks.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"testing"
"time"
"github.com/gorilla/mux"
)
// Config holds application configuration
type Config struct {
Port string "json:"port""
Environment string "json:"environment""
LogLevel string "json:"log_level""
DatabaseURL string "json:"database_url""
RedisURL string "json:"redis_url""
}
// App represents the main application
type App struct {
config *Config
router *mux.Router
server *http.Server
}
// NewApp creates a new application instance
func NewApp(config *Config) *App {
router := mux.NewRouter()
app := &App{
config: config,
router: router,
}
// Setup routes
app.setupRoutes()
// Setup server
app.server = &http.Server{
Addr: ":" + config.Port,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
return app
}
func (app *App) setupRoutes() {
// Health check endpoint
app.router.HandleFunc("/health", app.healthHandler).Methods("GET")
// Readiness check endpoint
app.router.HandleFunc("/ready", app.readyHandler).Methods("GET")
// Metrics endpoint
app.router.HandleFunc("/metrics", app.metricsHandler).Methods("GET")
// API routes
api := app.router.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/status", app.statusHandler).Methods("GET")
// Apply middleware
app.router.Use(app.loggingMiddleware)
app.router.Use(app.recoveryMiddleware)
}
func (app *App) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
response := map[string]interface{}{
"status": "healthy",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"version": "1.0.0",
"uptime": time.Since(startTime).String(),
}
json.NewEncoder(w).Encode(response)
}
func (app *App) readyHandler(w http.ResponseWriter, r *http.Request) {
// Check if application is ready to serve traffic
ready := app.checkReadiness()
w.Header().Set("Content-Type", "application/json")
if ready {
w.WriteHeader(http.StatusOK)
response := map[string]interface{}{
"status": "ready",
"message": "Application is ready to serve traffic",
}
json.NewEncoder(w).Encode(response)
} else {
w.WriteHeader(http.StatusServiceUnavailable)
response := map[string]interface{}{
"status": "not_ready",
"message": "Application is not ready",
}
json.NewEncoder(w).Encode(response)
}
}
func (app *App) metricsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
metrics := fmt.Sprintf("# HELP go_app_requests_total Total number of requests
" +
"# TYPE go_app_requests_total counter
" +
"go_app_requests_total{method="GET",endpoint="/health"} %d
" +
"go_app_requests_total{method="GET",endpoint="/ready"} %d
" +
"go_app_requests_total{method="GET",endpoint="/metrics"} %d
" +
"# HELP go_app_uptime_seconds Application uptime in seconds
" +
"# TYPE go_app_uptime_seconds gauge
" +
"go_app_uptime_seconds %f
",
healthRequests, readyRequests, metricsRequests,
time.Since(startTime).Seconds())
w.Write([]byte(metrics))
}
func (app *App) statusHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response := map[string]interface{}{
"status": "running",
"environment": app.config.Environment,
"port": app.config.Port,
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
json.NewEncoder(w).Encode(response)
}
func (app *App) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create response writer wrapper to capture status code
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(wrapped, r)
// Log request details
log.Printf("%s %s %d %v %s",
r.Method,
r.URL.Path,
wrapped.statusCode,
time.Since(start),
r.RemoteAddr)
})
}
func (app *App) recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func (app *App) checkReadiness() bool {
// In a real application, check:
// - Database connectivity
// - Redis connectivity
// - External service dependencies
// - Resource availability
// For demo purposes, always return true
return true
}
func (app *App) Start() error {
log.Printf("Starting application on port %s", app.config.Port)
log.Printf("Environment: %s", app.config.Environment)
return app.server.ListenAndServe()
}
func (app *App) Shutdown(ctx context.Context) error {
log.Println("Shutting down application...")
return app.server.Shutdown(ctx)
}
// Response writer wrapper to capture status code
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// Global variables for metrics
var (
startTime = time.Now()
healthRequests = 0
readyRequests = 0
metricsRequests = 0
)
func main() {
// Parse command line flags
port := flag.String("port", "8080", "Server port")
environment := flag.String("env", "development", "Environment (development/staging/production)")
flag.Parse()
// Load configuration
config := &Config{
Port: getEnv("PORT", *port),
Environment: getEnv("ENVIRONMENT", *environment),
LogLevel: getEnv("LOG_LEVEL", "info"),
DatabaseURL: getEnv("DATABASE_URL", ""),
RedisURL: getEnv("REDIS_URL", ""),
}
// Create application
app := NewApp(config)
// Setup graceful shutdown
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
// Start server in goroutine
go func() {
if err := app.Start(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Wait for shutdown signal
<-stop
// Graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := app.Shutdown(ctx); err != nil {
log.Printf("Error during shutdown: %v", err)
}
log.Println("Application stopped gracefully")
}
// Helper function to get environment variables
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// Dockerfile example:
/*
FROM golang:1.21-alpine 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 main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
*/
// docker-compose.yml example:
/*
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- ENVIRONMENT=production
- PORT=8080
- LOG_LEVEL=info
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
*/