Deployment Strategies for Go Applications
Deploying Go applications is straightforward due to Go’s ability to create static binaries. This guide covers various deployment strategies from simple binary deployment to container orchestration.
Building Go Applications
Basic Build
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}Build commands:
# Build for current platform
go build -o myapp .
# Build for specific OS and architecture
GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 .
GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe .
GOOS=darwin GOARCH=amd64 go build -o myapp-darwin-amd64 .
# Build with optimizations
go build -ldflags="-s -w" -o myapp . # Strip debug info
# Build with version info
go build -ldflags="-X main.version=1.2.3 -X main.buildTime=$(date -u +%Y%m%d%H%M%S)" -o myapp .Docker Deployment
Basic Dockerfile
# Multi-stage build
FROM golang:1.21-alpine AS builder
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 -ldflags="-s -w" -o myapp .
# Final stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy the binary from builder stage
COPY --from=builder /app/myapp .
# Expose port
EXPOSE 8080
# Run the binary
CMD ["./myapp"]Advanced Dockerfile with Configuration
FROM golang:1.21-alpine AS builder
# Install git (needed for private modules)
RUN apk add --no-cache git
WORKDIR /app
# Copy and download dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy source
COPY . .
# Build with build info
ARG VERSION=dev
ARG BUILD_TIME
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" \
-o myapp .
FROM alpine:latest
# Install necessary runtime dependencies
RUN apk --no-cache add ca-certificates tzdata
# Create non-root user
RUN adduser -D -s /bin/sh appuser
WORKDIR /home/appuser/
# Copy binary and config
COPY --from=builder /app/myapp .
COPY --from=builder /app/config.yaml .
# Change ownership
RUN chown appuser:appuser myapp config.yaml
# Switch to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["./myapp"]Building and Running
# Build Docker image
docker build -t myapp:latest .
# Build with build args
docker build \
--build-arg VERSION=1.2.3 \
--build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t myapp:1.2.3 .
# Run container
docker run -p 8080:8080 myapp:latest
# Run with environment variables
docker run -p 8080:8080 -e DATABASE_URL=postgres://... myapp:latestSystemd Service
Deploying as a systemd service on Linux:
Create systemd service file
[Unit]
Description=My Go Application
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=always
RestartSec=5
Environment=DATABASE_URL=postgres://localhost/myapp
Environment=PORT=8080
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/myapp/logs
ProtectHome=true
[Install]
WantedBy=multi-user.targetDeployment script
#!/bin/bash
# Stop service if running
sudo systemctl stop myapp || true
# Create application directory
sudo mkdir -p /opt/myapp
sudo chown appuser:appuser /opt/myapp
# Copy binary and config
sudo cp myapp /opt/myapp/
sudo cp config.yaml /opt/myapp/
sudo chown appuser:appuser /opt/myapp/*
# Copy systemd service file
sudo cp myapp.service /etc/systemd/system/
# Reload systemd and start service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
# Check status
sudo systemctl status myappCloud Deployment
AWS EC2 Deployment
#!/bin/bash
# Update system
sudo yum update -y
# Install Go (if not using container)
# wget https://golang.org/dl/go1.21.0.linux-amd64.tar.gz
# sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz
# export PATH=$PATH:/usr/local/go/bin
# Create application user
sudo useradd -m -s /bin/bash appuser
# Create application directory
sudo mkdir -p /opt/myapp
sudo chown appuser:appuser /opt/myapp
# Copy application files (use scp, rsync, or CI/CD)
# scp myapp ec2-user@instance:/opt/myapp/
# scp config.yaml ec2-user@instance:/opt/myapp/
# Install as systemd service
sudo cp myapp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
# Configure firewall
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
# Setup log rotation
sudo cp /etc/logrotate.d/myapp /etc/logrotate.d/
sudo logrotate /etc/logrotate.confHeroku Deployment
Create Procfile:
web: myappBuild with correct settings:
# Build for Heroku (dyno uses Linux)
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o myapp .
# Create .profile for runtime configuration
echo "export PORT=\$PORT" > .profileGoogle Cloud Run
# cloudbuild.yaml
steps:
- name: 'golang:1.21'
args: ['go', 'build', '-ldflags=-s -w', '-o', 'myapp', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA']
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'myapp'
- '--image'
- 'gcr.io/$PROJECT_ID/myapp:$COMMIT_SHA'
- '--region'
- 'us-central1'
- '--platform'
- 'managed'
- '--port'
- '8080'Kubernetes Deployment
Dockerfile for Kubernetes
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 -ldflags="-s -w" -o myapp .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]Kubernetes manifests
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: myapp-secrets
key: database-url
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
config.yaml: |
server:
host: "0.0.0.0"
port: 8080
log_level: "info"
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
data:
database-url: <base64-encoded-database-url>Deployment commands
# Apply manifests
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
# Check deployment
kubectl get pods
kubectl get services
kubectl logs -f deployment/myapp
# Scale deployment
kubectl scale deployment myapp --replicas=5
# Update deployment
kubectl set image deployment/myapp myapp=myapp:v2.0.0CI/CD with GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: Cache Go modules
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Build
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o myapp .
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myapp:latest
- name: Deploy to staging
if: github.ref == 'refs/heads/main'
run: |
echo "Deploy to staging environment"
# Add your deployment commands here
- name: Run tests on staging
if: github.ref == 'refs/heads/main'
run: |
# Health check
curl -f http://staging.example.com/health
# Run integration tests
# ...
- name: Deploy to production
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: |
echo "Deploy to production environment"
# Add your production deployment commands here Blue-Green Deployment
#!/bin/bash
# Blue-green deployment script
BLUE="myapp-blue"
GREEN="myapp-green"
# Determine which environment is live
if kubectl get service myapp-service -o jsonpath='{.spec.selector.version}' | grep -q blue; then
LIVE=$BLUE
STANDBY=$GREEN
LIVE_VERSION="blue"
STANDBY_VERSION="green"
else
LIVE=$GREEN
STANDBY=$BLUE
LIVE_VERSION="green"
STANDBY_VERSION="blue"
fi
echo "Live environment: $LIVE"
echo "Standby environment: $STANDBY"
# Deploy to standby environment
kubectl set image deployment/$STANDBY myapp=myapp:$NEW_VERSION
kubectl rollout status deployment/$STANDBY
# Wait for standby to be ready
kubectl wait --for=condition=available --timeout=300s deployment/$STANDBY
# Run smoke tests on standby
if curl -f http://standby.example.com/health; then
echo "Smoke tests passed"
else
echo "Smoke tests failed, rolling back"
kubectl rollout undo deployment/$STANDBY
exit 1
fi
# Switch traffic to standby
kubectl patch service myapp-service -p "{\"spec\":{\"selector\":{\"app\":\"myapp\",\"version\":\"$STANDBY_VERSION\"}}}"
# Wait for traffic switch
sleep 30
# Verify new version is working
if curl -f http://myapp.example.com/health; then
echo "Traffic switch successful"
# Scale down old version
kubectl scale deployment $LIVE --replicas=0
else
echo "Traffic switch failed, rolling back"
kubectl patch service myapp-service -p "{\"spec\":{\"selector\":{\"app\":\"myapp\",\"version\":\"$LIVE_VERSION\"}}}"
kubectl scale deployment $STANDBY --replicas=0
exit 1
fiMonitoring and Health Checks
package main
import (
"encoding/json"
"net/http"
"time"
)
type HealthStatus struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Version string `json:"version"`
Uptime string `json:"uptime"`
}
var startTime = time.Now()
func healthHandler(w http.ResponseWriter, r *http.Request) {
status := HealthStatus{
Status: "healthy",
Timestamp: time.Now(),
Version: version, // Set at build time
Uptime: time.Since(startTime).String(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}
func readinessHandler(w http.ResponseWriter, r *http.Request) {
// Check database connection, external services, etc.
if isReady() {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ready"))
} else {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("not ready"))
}
}
func isReady() bool {
// Implement your readiness checks
// - Database connection
// - External service availability
// - Required resources loaded
return true
}
func main() {
http.HandleFunc("/health", healthHandler)
http.HandleFunc("/ready", readinessHandler)
http.ListenAndServe(":8080", nil)
}Best Practices
- Use multi-stage Docker builds: Reduce image size
- Run as non-root user: Improve security
- Include health checks: For container orchestration
- Use environment variables: For configuration
- Implement graceful shutdown: Handle SIGTERM properly
- Log structured data: Use JSON logging
- Monitor resource usage: CPU, memory, disk
- Automate deployment: Use CI/CD pipelines
- Test deployments: Use staging environments
- Plan rollback strategies: Have backup plans
Go’s deployment story is excellent due to static binaries and simple runtime requirements. Choose the deployment strategy that best fits your infrastructure and operational needs.
For more on Docker, check our Docker tutorial. If you need to configure your application, see the configuration tutorial.
Last updated on