Production Deployment
Complete guide to deploying Promenade Platform in production environments with Docker, cloud hosting, monitoring, and scaling strategies.
Quick Deployment Options
Option 1: Docker Compose (Single Server)
Best for: Small teams, MVP stage, up to 1000 users
# 1. Clone repository
git clone https://github.com/basilex/promenade.git
cd promenade
# 2. Configure production environment
cp config/app.postgres-prod.yaml config/app.postgres-prod.local.yaml
vim config/app.postgres-prod.local.yaml # Set DB_PASSWORD, JWT_SECRET, etc.
# 3. Start production stack
docker compose -f docker/docker-compose.postgres.prod.yml up -d
# 4. Run migrations
docker exec promenade_api /app/bin/migrate up
# 5. Verify health
curl http://localhost:8081/healthOption 2: Kubernetes (Scalable)
Best for: Growth stage, 1000+ users, high availability
# 1. Apply Kubernetes manifests
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/redis.yaml
kubectl apply -f k8s/app.yaml
# 2. Check deployment
kubectl get pods -n promenade
kubectl logs -f deployment/promenade-api -n promenadeOption 3: Cloud Platforms
Best for: Quick MVP, managed infrastructure
- AWS: ECS Fargate + RDS PostgreSQL + ElastiCache Redis
- GCP: Cloud Run + Cloud SQL + Memorystore
- Azure: Container Apps + PostgreSQL + Redis Cache
Docker Deployment
Production Docker Compose
docker/docker-compose.postgres.prod.yml:
version: "3.8"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER:-promenade}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME:-promenade_prod}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U promenade"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
ports:
- "6379:6379"
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
api:
image: promenade/api:latest
build:
context: .
dockerfile: docker/Dockerfile
environment:
ENVIRONMENT: production
DB_HOST: postgres
DB_PORT: 5432
DB_USER: ${DB_USER:-promenade}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-promenade_prod}
REDIS_ADDR: redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
ports:
- "8081:8081"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8081/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
redis_data:Build Production Image
docker/Dockerfile (Multi-stage build):
# Stage 1: Build
FROM golang:1.23-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 bin/promenade ./cmd/api
# Stage 2: Runtime
FROM alpine:latest
RUN apk --no-cache add ca-certificates curl
WORKDIR /app
COPY --from=builder /app/bin/promenade .
COPY --from=builder /app/config ./config
COPY --from=builder /app/migrations ./migrations
EXPOSE 8081
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8081/health || exit 1
CMD ["./promenade"]Build and Push Image
# Build image
docker build -f docker/Dockerfile -t promenade/api:0.1.0 .
docker tag promenade/api:0.1.0 promenade/api:latest
# Push to registry (Docker Hub, ECR, GCR, ACR)
docker push promenade/api:0.1.0
docker push promenade/api:latestEnvironment Configuration
Production Config File
config/app.postgres-prod.yaml:
app:
name: "Promenade Platform"
environment: "production"
version: "1.0.0"
server:
host: "0.0.0.0"
port: 8081
read_timeout: 30s
write_timeout: 30s
database:
postgres:
host: "${DB_HOST}"
port: 5432
user: "${DB_USER}"
password: "${DB_PASSWORD}"
database: "${DB_NAME}"
ssl_mode: "require"
max_connections: 50
max_idle_connections: 10
connection_max_lifetime: 1h
redis:
addr: "${REDIS_ADDR}"
password: "${REDIS_PASSWORD}"
pool_size: 100
max_retries: 3
databases:
revocation: 0
bus: 1
cache: 2
sessions: 3
jwt:
secret: "${JWT_SECRET}"
access_token_duration: 15m
refresh_token_duration: 168h # 7 days
issuer: "promenade-platform"
bus:
adapter: "redis" # Distributed event bus
worker_pool_size: 50
buffer_size: 10000
retry_attempts: 5
retry_delay: 2s
retry_max_delay: 60s
retry_multiplier: 2.0
cache:
enabled: true
adapter: "redis"
prefix: "promenade:"
ttl:
reference: 86400 # 24 hours
user: 1800 # 30 minutes
session: 3600 # 1 hour
logging:
level: "info"
format: "json"
add_source: falseEnvironment Variables
Required secrets (store in .env.prod or secrets manager):
# Database
export DB_HOST=your-db-host.rds.amazonaws.com
export DB_USER=promenade
export DB_PASSWORD=your-secure-password-here
export DB_NAME=promenade_prod
# Redis
export REDIS_ADDR=your-redis.cache.amazonaws.com:6379
export REDIS_PASSWORD=your-redis-password
# JWT
export JWT_SECRET=$(openssl rand -base64 32)
# Optional
export SENTRY_DSN=https://your-sentry-dsn
export SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/WEBHOOKLoad secrets:
# From file (development/staging)
source .env.prod
# From AWS Secrets Manager (production)
aws secretsmanager get-secret-value --secret-id promenade-prod | jq -r '.SecretString'
# From Google Secret Manager
gcloud secrets versions access latest --secret="promenade-prod"
# From Azure Key Vault
az keyvault secret show --vault-name promenade-vault --name prod-secretsCloud Platform Deployment
AWS ECS Fargate
1. Infrastructure Setup:
# Create VPC, subnets, security groups
terraform init
terraform plan -var-file=prod.tfvars
terraform apply
# Or use CloudFormation
aws cloudformation create-stack \
--stack-name promenade-infrastructure \
--template-body file://cloudformation/infrastructure.yaml2. Database (RDS PostgreSQL):
# Create database instance
aws rds create-db-instance \
--db-instance-identifier promenade-prod \
--db-instance-class db.t3.medium \
--engine postgres \
--engine-version 16.1 \
--master-username promenade \
--master-user-password $DB_PASSWORD \
--allocated-storage 100 \
--storage-type gp3 \
--backup-retention-period 7 \
--multi-az \
--vpc-security-group-ids sg-xxxxxxxx3. Cache (ElastiCache Redis):
# Create Redis cluster
aws elasticache create-cache-cluster \
--cache-cluster-id promenade-redis \
--cache-node-type cache.t3.micro \
--engine redis \
--engine-version 7.0 \
--num-cache-nodes 1 \
--security-group-ids sg-xxxxxxxx4. Container (ECS Fargate):
# Create ECS cluster
aws ecs create-cluster --cluster-name promenade-prod
# Register task definition
aws ecs register-task-definition --cli-input-json file://ecs-task-definition.json
# Create service
aws ecs create-service \
--cluster promenade-prod \
--service-name promenade-api \
--task-definition promenade-api:1 \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}"5. Load Balancer (ALB):
# Create target group
aws elbv2 create-target-group \
--name promenade-api-tg \
--protocol HTTP \
--port 8081 \
--vpc-id vpc-xxxxxxxx \
--target-type ip \
--health-check-path /health
# Create load balancer
aws elbv2 create-load-balancer \
--name promenade-alb \
--subnets subnet-xxx subnet-yyy \
--security-groups sg-xxxxxxxx \
--scheme internet-facingGoogle Cloud Platform (GCP)
1. Cloud SQL (PostgreSQL):
# Create database instance
gcloud sql instances create promenade-prod \
--database-version=POSTGRES_16 \
--tier=db-custom-2-7680 \
--region=us-central1 \
--backup \
--availability-type=REGIONAL
# Create database
gcloud sql databases create promenade_prod --instance=promenade-prod2. Memorystore (Redis):
# Create Redis instance
gcloud redis instances create promenade-redis \
--size=1 \
--region=us-central1 \
--redis-version=redis_7_0 \
--tier=standard3. Cloud Run:
# Deploy service
gcloud run deploy promenade-api \
--image gcr.io/your-project/promenade:latest \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars ENVIRONMENT=production,DB_HOST=$DB_HOST \
--set-secrets DB_PASSWORD=promenade-db-password:latest,JWT_SECRET=promenade-jwt:latest \
--min-instances 1 \
--max-instances 10 \
--cpu 2 \
--memory 4GiAzure
1. Azure Database for PostgreSQL:
# Create resource group
az group create --name promenade-prod --location eastus
# Create database server
az postgres flexible-server create \
--resource-group promenade-prod \
--name promenade-db \
--location eastus \
--admin-user promenade \
--admin-password $DB_PASSWORD \
--sku-name Standard_D2s_v3 \
--tier GeneralPurpose \
--version 162. Azure Cache for Redis:
# Create Redis cache
az redis create \
--resource-group promenade-prod \
--name promenade-redis \
--location eastus \
--sku Standard \
--vm-size c13. Container Apps:
# Create container app environment
az containerapp env create \
--name promenade-env \
--resource-group promenade-prod \
--location eastus
# Deploy app
az containerapp create \
--name promenade-api \
--resource-group promenade-prod \
--environment promenade-env \
--image your-registry.azurecr.io/promenade:latest \
--target-port 8081 \
--ingress external \
--min-replicas 1 \
--max-replicas 5 \
--cpu 1 \
--memory 2GiDatabase Migrations in Production
Pre-Deployment Checklist
- Backup database before migrations
- Test migrations in staging environment
- Review migration SQL for performance impact
- Plan rollback strategy
- Schedule downtime if needed (or use zero-downtime strategy)
Running Migrations
Option 1: Manual (via container):
# Connect to running API container
docker exec -it promenade_api bash
# Run migrations
./bin/migrate up
# Check status
./bin/migrate statusOption 2: Automated (CI/CD):
# .github/workflows/deploy.yml
- name: Run Migrations
run: |
docker run --rm \
-e DB_HOST=${{ secrets.DB_HOST }} \
-e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
promenade/api:${{ github.sha }} \
/app/bin/migrate upOption 3: Kubernetes Job:
# k8s/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: promenade-migration
spec:
template:
spec:
containers:
- name: migrate
image: promenade/api:latest
command: ["/app/bin/migrate", "up"]
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: promenade-secrets
key: db-host
restartPolicy: OnFailureZero-Downtime Migrations
Strategy:
- Backward-compatible changes: Add new columns (nullable), new tables
- Deploy new code: App works with old and new schema
- Run migration: Add columns, backfill data
- Remove old code: Clean up deprecated fields
Example:
-- Migration 1: Add new column (nullable)
ALTER TABLE customer_customers ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;
-- Deploy new code (reads/writes email_verified)
-- Migration 2: Backfill data
UPDATE customer_customers SET email_verified = TRUE WHERE email IS NOT NULL;
-- Migration 3: Make NOT NULL
ALTER TABLE customer_customers ALTER COLUMN email_verified SET NOT NULL;Monitoring & Observability
Health Checks
Kubernetes Probes:
livenessProbe:
httpGet:
path: /health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/db
port: 8081
initialDelaySeconds: 10
periodSeconds: 5Load Balancer Health:
- ALB:
/healthevery 30s - NGINX:
health_checkdirective - HAProxy:
option httpchk GET /health
Logging
Centralized Logging (ELK Stack, CloudWatch, Stackdriver):
# Fluent Bit config (ship logs to Elasticsearch)
[OUTPUT]
Name es
Match *
Host elasticsearch.prod.internal
Port 9200
Index promenade-logs
Type _docStructured Logs (JSON format in production):
{
"timestamp": "2025-12-29T10:15:30Z",
"level": "error",
"message": "Failed to create customer",
"context": {
"user_id": "01JGABC123",
"request_id": "req-xyz",
"error": "database connection timeout"
}
}Metrics
Prometheus Metrics (future):
// Instrument code with metrics
httpRequestsTotal.WithLabelValues("POST", "/customers").Inc()
httpRequestDuration.WithLabelValues("POST", "/customers").Observe(duration.Seconds())Grafana Dashboard:
- Request rate (req/s)
- Response time (p50, p95, p99)
- Error rate (%)
- Database connections (active/idle)
- Redis operations (get/set rate)
Error Tracking
Sentry Integration (future):
import "github.com/getsentry/sentry-go"
sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"),
Environment: "production",
})
// Capture errors
sentry.CaptureException(err)Scaling Strategies
Horizontal Scaling (Multiple Instances)
Benefits:
- Handle more traffic
- High availability (redundancy)
- Rolling deployments (zero downtime)
Implementation:
# Docker Swarm
docker service scale promenade_api=5
# Kubernetes
kubectl scale deployment promenade-api --replicas=5
# ECS
aws ecs update-service --service promenade-api --desired-count 5Auto-scaling:
# Kubernetes HPA (Horizontal Pod Autoscaler)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: promenade-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: promenade-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Database Scaling
Read Replicas:
- Route read queries to replicas
- Write queries to primary
- Reduce load on primary database
Connection Pooling:
- Use PgBouncer for PostgreSQL
- Max connections: 100 (application pool)
- Database max connections: 200
Query Optimization:
- Add indexes for common queries
- Use EXPLAIN ANALYZE for slow queries
- Enable query caching (Redis)
Redis Scaling
Cluster Mode:
- Sharding for large datasets
- High availability with replicas
- Automatic failover
Configuration:
redis:
mode: cluster
master_replicas: 3
shards: 3
read_from_replicas: trueSecurity Checklist
Pre-Production Security
- [ ] HTTPS Enforced: TLS 1.3, valid SSL certificate
- [ ] Secrets Rotation: Regular rotation of JWT secret, DB password
- [ ] Database Encryption: Encryption at rest (RDS, Cloud SQL)
- [ ] Network Isolation: Private subnets for DB/Redis
- [ ] Firewall Rules: Whitelist only necessary IPs
- [ ] Rate Limiting: Protect authentication endpoints (5 req/min)
- [ ] CORS Configuration: Allow only trusted domains
- [ ] SQL Injection Prevention: Parameterized queries (sqlx)
- [ ] XSS Protection: Input validation, output escaping
- [ ] CSRF Tokens: For state-changing requests
- [ ] Audit Logging: Log sensitive operations (user creation, role changes)
- [ ] Dependency Scanning:
go list -m all | nancyfor vulnerabilities
TLS/SSL Setup
Let's Encrypt (Free Certificate):
# Install Certbot
sudo apt-get install certbot
# Generate certificate
sudo certbot certonly --standalone -d api.promenade.com
# Configure NGINX
server {
listen 443 ssl;
server_name api.promenade.com;
ssl_certificate /etc/letsencrypt/live/api.promenade.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.promenade.com/privkey.pem;
location / {
proxy_pass http://localhost:8081;
}
}CI/CD Pipeline
GitHub Actions Workflow
.github/workflows/deploy.yml:
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@v4
with:
go-version: "1.23"
- name: Run tests
run: make test
- name: Run linter
run: make lint
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -f docker/Dockerfile -t promenade/api:${{ github.sha }} .
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push promenade/api:${{ github.sha }}
docker tag promenade/api:${{ github.sha }} promenade/api:latest
docker push promenade/api:latest
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster promenade-prod \
--service promenade-api \
--force-new-deploymentBackup & Disaster Recovery
Database Backups
Automated Backups:
# RDS PostgreSQL (AWS)
aws rds modify-db-instance \
--db-instance-identifier promenade-prod \
--backup-retention-period 30 \
--preferred-backup-window "03:00-04:00"
# Cloud SQL (GCP)
gcloud sql instances patch promenade-prod \
--backup-start-time=03:00Manual Backup:
# pg_dump backup
pg_dump -h your-db-host.com -U promenade promenade_prod > backup_$(date +%Y%m%d).sql
# Upload to S3
aws s3 cp backup_$(date +%Y%m%d).sql s3://promenade-backups/Restore:
# Download backup
aws s3 cp s3://promenade-backups/backup_20250129.sql .
# Restore database
psql -h your-db-host.com -U promenade promenade_prod < backup_20250129.sqlPoint-in-Time Recovery
Enable PITR (RDS, Cloud SQL):
- Restore to any second within retention period
- Useful for accidental data deletion
- Retention: 7-35 days
Cost Optimization
Resource Sizing
Start Small:
- API: 2 vCPU, 4GB RAM (1-2 instances)
- DB: db.t3.medium (2 vCPU, 4GB RAM)
- Redis: cache.t3.micro (1GB RAM)
- Est. Cost: $200-300/month (AWS)
Scale as Needed:
- Monitor CPU/Memory usage
- Upgrade when consistently > 70%
- Use auto-scaling to save costs
Cost-Saving Tips
- Reserved Instances: 30-60% savings (1-year commitment)
- Spot Instances: 70% savings for non-critical workloads
- S3 Intelligent Tiering: Auto-move cold data to cheaper storage
- CloudFront CDN: Cache static assets, reduce bandwidth
- Lambda Functions: Use for infrequent tasks (migrations, cron jobs)
Troubleshooting
Common Issues
1. Database Connection Failures
# Check database reachability
psql -h $DB_HOST -U $DB_USER -d $DB_NAME
# Check security group rules (AWS)
aws ec2 describe-security-groups --group-ids sg-xxxxxxxx
# Check logs
docker logs promenade_api | grep "database"2. Redis Connection Errors
# Test Redis connection
redis-cli -h $REDIS_ADDR -a $REDIS_PASSWORD ping
# Check Redis memory usage
redis-cli -h $REDIS_ADDR -a $REDIS_PASSWORD INFO memory3. High CPU/Memory Usage
# Check container stats
docker stats promenade_api
# Kubernetes
kubectl top pods -n promenade4. Slow API Responses
# Enable query logging (PostgreSQL)
ALTER DATABASE promenade_prod SET log_min_duration_statement = 1000; # Log queries > 1s
# Check slow queries
SELECT query, mean_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;Related Documentation
- Getting Started - Local development setup
- Development Workflow - Git workflow, testing
- Health Checks - Health monitoring
- Configuration Reference - Config options
- API Reference - REST API docs
Questions? Open a discussion
Need help deploying? Email: alexander.vasilenko@gmail.com