Deploying Secure Go Apps on GCP: A Journey from Local to Cloud

Muhammetberdi Jepbarov - Feb 8 - - Dev Community

Image description

It was 3 AM, and I was staring at my computer screen, trying to figure out why our Go application kept crashing in production. Our small e-commerce startup had just experienced its first major traffic spike, and our hosting setup wasn’t ready for it. That night changed how I thought about deploying Go applications forever.

The Problem We Faced

Our team had built a simple but powerful inventory management system using Go. It worked perfectly on our local machines, but when we deployed it to production, we faced several challenges:

  • Random crashes during peak hours
  • Security vulnerabilities we hadn’t considered
  • Scaling issues that cost us customers
  • Credentials being accidentally exposed in our code

Enter Google Cloud Platform (GCP)

After that difficult night, we decided to rebuild our deployment process using GCP. Here’s the story of how we transformed our application deployment from a source of stress into a smooth, secure operation.

Setting Up a Secure Foundation

First, let’s look at how we structured our Go application for secure deployment. Here’s a basic example of our initial setup:

`package main
import (
"log"
"os"
"net/http"
"github.com/joho/godotenv"
)
func main() {
// Load environment variables
if err := godotenv.Load(); err != nil {
log.Printf("No .env file found")
}

// Get port from environment variable
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

http.HandleFunc("/", handleHome)
log.Printf("Starting server on port %s", port)
log.Fatal(http.ListenAndServe(":" + port, nil))
}
func handleHome(w http.ResponseWriter, r *http.Request) {
// Implement secure headers
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Security-Policy", "default-src 'self'")

w.Write([]byte("Welcome to our secure Go application!"))
}`

Securing Secrets with GCP Secret Manager

One of our biggest concerns was managing secrets. Instead of storing sensitive data in environment variables, we moved to GCP Secret Manager:
`
import (
secretmanager "cloud.google.com/go/secretmanager/apiv1"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)
func getSecret(projectID, secretID string) (string, error) {
ctx := context.Background()
client, err := secretmanager.NewClient(ctx)
if err != nil {
return "", err
}
defer client.Close()

name := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", projectID, secretID)
req := &secretmanagerpb.AccessSecretVersionRequest{
Name: name,
}

result, err := client.AccessSecretVersion(ctx, req)
if err != nil {
return "", err
}

return string(result.Payload.Data), nil
}`

Deploying to Cloud Run

We chose Cloud Run for deployment because it offered:

  1. Automatic scaling
  2. Pay-per-use pricing
  3. Built-in security features
  4. Simple deployment process

Here’s our Dockerfile that we use for deployment:
`
FROM golang:1.21-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .

Use a minimal alpine image for the final stage

FROM alpine:latest
WORKDIR /app
COPY - from=0 /app/main .

Run as non-root user

RUN adduser -D appuser
USER appuser
CMD ["./main"]`

Implementing Security Best Practices

We learned to follow these key security practices:

  1. Always use HTTPS
  2. Implement rate limiting
  3. Use proper logging
  4. Regular security scanning
  5. Implement proper authentication

Here’s an example of how we implemented rate limiting:

import (
"golang.org/x/time/rate"
"sync"
)
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu *sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
mu: &sync.RWMutex{},
r: r,
b: b,
}
}
func (i *IPRateLimiter) AddIP(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter := rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
return limiter
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
limiter, exists := i.ips[ip]
if !exists {
i.mu.Unlock()
return i.AddIP(ip)
}
i.mu.Unlock()
return limiter
}

The Results

After implementing these changes:

  • Our application handled traffic spikes smoothly
  • Security vulnerabilities were significantly reduced
  • Deployment became a straightforward process
  • Our team could sleep better at night!

Where Can You Use This?

This setup is perfect for:

  • E-commerce platforms
  • API services
  • Web applications
  • Microservices
  • Data processing systems
  • Business automation tools

Conclusion

That 3 AM incident taught us valuable lessons about deploying Go applications securely. By leveraging GCP’s features and following security best practices, we transformed our deployment process from a source of stress into a reliable system.

Remember: security isn’t a one-time setup but a continuous journey. Keep learning, keep improving, and most importantly, keep your applications secure.

Want to learn more? Check out the official Go documentation and GCP’s security best practices guide for more detailed information.

. . . . . . . . .