Deployment
This guide covers deploying GOE applications to production environments, including Docker, cloud platforms, and monitoring setup.
Production Checklist
Before deploying to production:
- [ ] Environment Configuration: Set up production environment variables
- [ ] Database: Configure production database with proper connection pooling
- [ ] Logging: Set up structured logging with appropriate log levels
- [ ] Security: Enable HTTPS, set up authentication, validate all inputs
- [ ] Monitoring: Configure health checks, metrics, and alerting
- [ ] Performance: Set up caching, optimize database queries
- [ ] Backup: Implement database backup and recovery procedures
Docker Deployment
Dockerfile
dockerfile
# Multi-stage build for production
FROM golang:1.21-alpine AS builder
# Install build dependencies
RUN apk add --no-cache git ca-certificates
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
# Runtime stage
FROM alpine:latest
# Install runtime dependencies
RUN apk --no-cache add ca-certificates tzdata
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Copy binary from builder
COPY --from=builder /app/main .
COPY --from=builder /app/.env.example .env
# Change ownership
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["./main"]
Docker Compose
yaml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- APP_ENV=production
- HTTP_PORT=8080
- DB_HOST=db
- CACHE_DRIVER=redis
- REDIS_HOST=redis
depends_on:
- db
- redis
restart: unless-stopped
networks:
- app-network
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
restart: unless-stopped
networks:
- app-network
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
networks:
- app-network
volumes:
db_data:
redis_data:
networks:
app-network:
driver: bridge
Production Environment Variables
bash
# .env.production
APP_ENV=production
APP_NAME=My GOE App
APP_VERSION=1.0.0
# HTTP Server
HTTP_HOST=0.0.0.0
HTTP_PORT=8080
HTTP_READ_TIMEOUT=30s
HTTP_WRITE_TIMEOUT=30s
# Database
DB_DRIVER=postgres
DB_HOST=db
DB_PORT=5432
DB_NAME=myapp
DB_USER=appuser
DB_PASSWORD=secure_password_here
DB_SSL_MODE=require
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
# Cache
CACHE_DRIVER=redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=redis_password_here
# Logging
LOG_LEVEL=warn
LOG_FORMAT=json
# Security
JWT_SECRET=super_secret_jwt_key_here
BCRYPT_COST=12
Cloud Deployment
AWS ECS
json
{
"family": "goe-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "goe-app",
"image": "your-registry/goe-app:latest",
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"environment": [
{
"name": "APP_ENV",
"value": "production"
},
{
"name": "HTTP_PORT",
"value": "8080"
}
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:db-password"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/goe-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3
}
}
]
}
Kubernetes
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: goe-app
spec:
replicas: 3
selector:
matchLabels:
app: goe-app
template:
metadata:
labels:
app: goe-app
spec:
containers:
- name: goe-app
image: your-registry/goe-app:latest
ports:
- containerPort: 8080
env:
- name: APP_ENV
value: "production"
- name: HTTP_PORT
value: "8080"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: goe-app-service
spec:
selector:
app: goe-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: goe-app-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.com
secretName: goe-app-tls
rules:
- host: myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: goe-app-service
port:
number: 80
Health Checks
Health Check Handler
go
package handler
import (
"context"
"time"
"github.com/gofiber/fiber/v3"
"go.oease.dev/goe/v2/contract"
)
type HealthHandler struct {
db contract.DB
cache contract.Cache
logger contract.Logger
}
func NewHealthHandler(db contract.DB, cache contract.Cache, logger contract.Logger) *HealthHandler {
return &HealthHandler{db: db, cache: cache, logger: logger}
}
func (h *HealthHandler) HealthCheck(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
checks := map[string]interface{}{
"status": "healthy",
"timestamp": time.Now().Unix(),
"checks": map[string]interface{}{
"database": h.checkDatabase(ctx),
"cache": h.checkCache(ctx),
},
}
// Check if any service is unhealthy
for _, check := range checks["checks"].(map[string]interface{}) {
if status, ok := check.(map[string]interface{})["status"]; ok && status != "healthy" {
checks["status"] = "unhealthy"
return c.Status(503).JSON(checks)
}
}
return c.JSON(checks)
}
func (h *HealthHandler) checkDatabase(ctx context.Context) interface{} {
if err := h.db.Instance().WithContext(ctx).Exec("SELECT 1").Error; err != nil {
h.logger.Error("Database health check failed", "error", err)
return map[string]interface{}{
"status": "unhealthy",
"error": err.Error(),
}
}
return map[string]interface{}{
"status": "healthy",
}
}
func (h *HealthHandler) checkCache(ctx context.Context) interface{} {
testKey := "health_check"
testValue := "ok"
if err := h.cache.Set(testKey, testValue, 10*time.Second); err != nil {
h.logger.Error("Cache health check failed", "error", err)
return map[string]interface{}{
"status": "unhealthy",
"error": err.Error(),
}
}
if _, err := h.cache.Get(testKey); err != nil {
h.logger.Error("Cache read health check failed", "error", err)
return map[string]interface{}{
"status": "unhealthy",
"error": err.Error(),
}
}
return map[string]interface{}{
"status": "healthy",
}
}
Monitoring Setup
Prometheus Metrics
go
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
RequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
RequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
ActiveConnections = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "active_connections",
Help: "Number of active connections",
},
)
)
func RecordMetrics(method, path string, status int, duration float64) {
RequestsTotal.WithLabelValues(method, path, fmt.Sprintf("%d", status)).Inc()
RequestDuration.WithLabelValues(method, path).Observe(duration)
}
Grafana Dashboard
json
{
"dashboard": {
"title": "GOE Application Dashboard",
"panels": [
{
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total[5m])",
"legendFormat": "{{method}} {{path}}"
}
]
},
{
"title": "Error Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total{status=~\"4..|5..\"}[5m])",
"legendFormat": "Error Rate"
}
]
},
{
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
}
]
}
]
}
}
Security Configuration
HTTPS/TLS
nginx
# nginx.conf
server {
listen 80;
server_name myapp.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://app:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Rate Limiting
go
import "github.com/gofiber/fiber/v3/middleware/limiter"
func SetupRateLimiting(app *fiber.App) {
// Global rate limiting
app.Use(limiter.New(limiter.Config{
Max: 100,
Expiration: 1 * time.Minute,
KeyGenerator: func(c fiber.Ctx) string {
return c.Get("X-Forwarded-For")
},
}))
// API rate limiting
api := app.Group("/api")
api.Use(limiter.New(limiter.Config{
Max: 1000,
Expiration: 1 * time.Hour,
KeyGenerator: func(c fiber.Ctx) string {
return c.Get("Authorization") // Use token for authenticated limits
},
}))
}
Backup and Recovery
Database Backup
bash
#!/bin/bash
# backup.sh
DB_NAME=${DB_NAME:-myapp}
DB_USER=${DB_USER:-appuser}
DB_HOST=${DB_HOST:-localhost}
BACKUP_DIR=${BACKUP_DIR:-/backups}
# Create backup directory
mkdir -p $BACKUP_DIR
# Generate backup filename
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/db_backup_$DATE.sql"
# Create backup
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > $BACKUP_FILE
# Compress backup
gzip $BACKUP_FILE
# Remove old backups (keep last 7 days)
find $BACKUP_DIR -name "db_backup_*.sql.gz" -mtime +7 -delete
echo "Backup completed: $BACKUP_FILE.gz"
Recovery Script
bash
#!/bin/bash
# restore.sh
BACKUP_FILE=$1
DB_NAME=${DB_NAME:-myapp}
DB_USER=${DB_USER:-appuser}
DB_HOST=${DB_HOST:-localhost}
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup_file.sql.gz>"
exit 1
fi
# Extract backup
gunzip -c $BACKUP_FILE > temp_restore.sql
# Restore database
psql -h $DB_HOST -U $DB_USER -d $DB_NAME < temp_restore.sql
# Cleanup
rm temp_restore.sql
echo "Database restored from $BACKUP_FILE"
CI/CD Pipeline
GitHub Actions
yaml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: Run tests
run: go test -v -race ./...
- name: Run linter
uses: golangci/golangci-lint-action@v3
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t ${{ secrets.DOCKER_REGISTRY }}/goe-app:${{ github.sha }} .
docker tag ${{ secrets.DOCKER_REGISTRY }}/goe-app:${{ github.sha }} ${{ secrets.DOCKER_REGISTRY }}/goe-app:latest
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push ${{ secrets.DOCKER_REGISTRY }}/goe-app:${{ github.sha }}
docker push ${{ secrets.DOCKER_REGISTRY }}/goe-app:latest
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
# Update Kubernetes deployment
kubectl set image deployment/goe-app goe-app=${{ secrets.DOCKER_REGISTRY }}/goe-app:${{ github.sha }}
kubectl rollout status deployment/goe-app
Best Practices
- Zero-downtime deployments: Use rolling updates or blue-green deployments
- Environment isolation: Separate staging and production environments
- Secret management: Use secure secret management systems
- Monitoring: Set up comprehensive monitoring and alerting
- Backup: Implement automated backup and recovery procedures
- Security: Keep dependencies updated and scan for vulnerabilities
- Performance: Monitor and optimize application performance
- Documentation: Maintain deployment and operational documentation
Next Steps
- Best Practices - Follow production best practices