🛡️ Building a Fortified Microservice: The Microservices Workshop (Part 1/3)
Stop deploying insecure containers. Learn how to architect a 'Fortified' microservice on GCP with Private Cloud SQL, WAF protection, and zero-trust ingress.
🛡️ Building a Fortified Microservice: The Microservices Workshop (Part 1/3)
Most cloud tutorials follow a path of “least resistance.” They show you how to get a container running as fast as possible, often sacrificing security for simplicity. You end up with public databases, wide-open ingress, and default service accounts.
In the real world, this is a liability.
Welcome to Part 1 of the Microservices Workshop Series. Today, we aren’t just deploying Go code; we are building a Fortress.
👉 View the full source code on GitHub 👉 Technical Documentation Table of Contents
🏗️ The Blueprint: Security by Design
Before writing a single line of code, we defined a “Zero Trust” architecture for our microservice. Here is how it looks:
💡 The Core Pillars
- Isolation: The database has NO public IP. It lives entirely in a private VPC.
- Identity: The application runs under a dedicated Service Account with the absolute minimum permissions (Least Privilege).
- Edge Security: A Global Load Balancer combined with Cloud Armor acts as a Web Application Firewall (WAF) to block SQL Injection at the door.
- Closed Backdoor: Cloud Run is configured to reject any traffic that doesn’t come directly from our Load Balancer.
🚀 The Journey
I’ve structured this workshop into logical modules. Each step is detailed below so you can follow along.
📋 Prerequisites
- Go 1.25.6+ installed.
- Google Cloud SDK (
gcloud) installed and authenticated. - Cloud SQL Auth Proxy installed.
- PostgreSQL Client (
psql) installed. - Billing enabled on your Google Cloud Project.
Automated Setup Scripts
I’ve created scripts to verify and install dependencies. You can create a file named setup.sh (Linux) or setup_mac.sh (macOS) with the following content:
Linux (setup.sh):
1
2
3
4
5
6
7
#!/bin/bash
set -e
echo "🛠️ Checking and Installing Dependencies..."
read -p "This script will install Go 1.25.6, Google Cloud SDK, Cloud SQL Proxy, and PostgreSQL Client. Do you want to proceed? (y/N) " response
if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then exit 1; fi
# ... (Install commands for Go, gcloud, Proxy, psql)
echo "🎉 Dependency check complete!"
Run it with chmod +x setup.sh && ./setup.sh.
🛠️ Part 1: The Foundation (Database)
We start by creating the storage layer. We will use Cloud SQL (PostgreSQL).
1. Environmental Setup
Open your terminal and set these variables to save time later.
1
2
3
4
5
6
export PROJECT_ID="your-project-id-here"
export REGION="us-central1"
export DB_PASS="<YOUR_SECURE_PASSWORD>" # ⚠️ CHANGE THIS!
export INSTANCE_NAME="workshop-db"
gcloud config set project $PROJECT_ID
2. Authentication
Log in to Google Cloud and set up application default credentials.
1
2
gcloud auth login --no-launch-browser
gcloud auth application-default login --project $PROJECT_ID --no-launch-browser
3. Enable Google Cloud APIs
1
2
3
4
5
6
gcloud services enable \
sqladmin.googleapis.com \
run.googleapis.com \
compute.googleapis.com \
servicenetworking.googleapis.com \
logging.googleapis.com
4. Network Setup
We need a secure private network (VPC) for our services to communicate.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. Create VPC and Subnet
gcloud compute networks create workshop-vpc --subnet-mode=custom
gcloud compute networks subnets create workshop-subnet \
--network=workshop-vpc \
--range=10.0.0.0/24 \
--region=$REGION
# 2. Configure Private Service Access (for Cloud SQL)
gcloud compute addresses create google-managed-services-default \
--global \
--purpose=VPC_PEERING \
--prefix-length=16 \
--network=workshop-vpc
gcloud services vpc-peerings connect \
--service=servicenetworking.googleapis.com \
--ranges=google-managed-services-default \
--network=workshop-vpc
5. Create Database Instance & User
Note: This step takes 5-10 minutes.
1
2
3
4
5
6
7
8
9
10
11
12
# Create the instance
gcloud sql instances create $INSTANCE_NAME \
--database-version=POSTGRES_16 \
--tier=db-f1-micro \
--edition=ENTERPRISE \
--region=$REGION \
--root-password=$DB_PASS \
--network=workshop-vpc \
--no-assign-ip
# Create the specific database
gcloud sql databases create users_db --instance=$INSTANCE_NAME
6. Seed the Data
Since we disabled the public IP, we connect via the Cloud SQL Auth Proxy.
- Enable Public IP (Temporarily):
gcloud sql instances patch $INSTANCE_NAME --assign-ip - Start Proxy:
./cloud-sql-proxy --port=5433 $PROJECT_ID:$REGION:$INSTANCE_NAME - Run SQL:
1
PGPASSWORD=$DB_PASS psql --host=127.0.0.1 --port=5433 --username=postgres --dbname=postgres
- SQL Commands:
1 2 3 4 5 6 7
CREATE USER go_workshop WITH PASSWORD '<YOUR_SECURE_PASSWORD>'; ALTER DATABASE users_db OWNER TO go_workshop; \c users_db; GRANT ALL ON SCHEMA public TO go_workshop; CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, username VARCHAR(50), email VARCHAR(100)); ALTER TABLE users OWNER TO go_workshop; INSERT INTO users (username, email) VALUES ('cloud_runner', 'runner@example.com');
- Disable Public IP:
gcloud sql instances patch $INSTANCE_NAME --no-assign-ip
💻 Part 2: The Application (Go)
We build a Go service using Dependency Injection and a clean directory structure.
1. Initialize Project
1
2
3
4
mkdir go-workshop && cd go-workshop
go mod init github.com/youruser/go-workshop
mkdir models handlers middleware
go get github.com/jackc/pgx/v4
2. The Code
We implement models/user.go (Structs), middleware/basic_auth.go (Security), and handlers/user_handler.go (Business Logic).
The Entry Point (main.go):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
// ... imports ...
func main() {
// Connect to DB using standard PGX driver
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_NAME"))
db, _ := sql.Open("pgx", dsn)
// Wrap Handler with Middleware
authHandler := &middleware.BasicAuth{
Username: os.Getenv("AUTH_USER"),
Password: os.Getenv("AUTH_PASS"),
Next: &handlers.UserHandler{DB: db},
}
http.ListenAndServe(":"+os.Getenv("PORT"), authHandler)
}
🤖 AI-Assisted Testing: One of the coolest parts of this workflow is that all unit tests were generated by Gemini. By using standard interfaces and dependency injection, we could simply ask the AI: “Generate table-driven tests for this handler using go-sqlmock”, and it produced robust, ready-to-run test code.
3. Containerization
We create a multi-stage Dockerfile using golang:1.25.6-trixie for building and debian:trixie-slim for the runtime to ensure a small, secure footprint.
🚀 Part 4: Deployment (Cloud Run)
1. Service Account Setup
The app needs an identity. We create workshop-sa and grant it only roles/cloudsql.client.
1
2
3
4
gcloud iam service-accounts create workshop-sa --display-name="Workshop SA"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:workshop-sa@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"
2. Deploy
We build the container and deploy it, connecting it to our VPC so it can reach the private database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Build
cd go-workshop
gcloud builds submit --tag gcr.io/$PROJECT_ID/go-workshop
cd ..
# Get Private IP
export DB_HOST=$(gcloud sql instances describe $INSTANCE_NAME \
--flatten="ipAddresses[]" \
--format="csv[no-heading](ipAddresses.ipAddress, ipAddresses.type)" | grep ",PRIVATE" | cut -d',' -f1)
# Deploy with Direct VPC Egress
gcloud run deploy go-service \
--image gcr.io/$PROJECT_ID/go-workshop \
--region $REGION \
--allow-unauthenticated \
--service-account workshop-sa@$PROJECT_ID.iam.gserviceaccount.com \
--network=workshop-vpc \
--subnet=workshop-subnet \
--set-env-vars DB_HOST="$DB_HOST" \
--set-env-vars DB_PORT="5432" \
--set-env-vars DB_USER="go_workshop" \
--set-env-vars DB_NAME="users_db" \
--set-env-vars DB_PASS="$DB_PASS" \
--set-env-vars AUTH_USER="admin" \
--set-env-vars AUTH_PASS="<YOUR_AUTH_PASSWORD>"
🌐 Part 5 & 6: The Shield (Load Balancer & Cloud Armor)
Finally, we put the service behind a Global Load Balancer and attach a Cloud Armor security policy.
- Reserve IP:
gcloud compute addresses create workshop-lb-ip --global - Create NEG:
gcloud compute network-endpoint-groups create go-service-neg ... - Setup Load Balancer: Create Backend Service, URL Map, Proxy, and Forwarding Rule.
- Cloud Armor: Create a policy
workshop-armor-policywith ruleevaluatePreconfiguredExpr('sqli-stable')to block SQL injection. - Restrict Ingress:
1
gcloud run services update go-service --region $REGION --ingress internal-and-cloud-load-balancing
✅ Final Verification
Test your fortified service:
- Valid Request:
curl -u admin:<PASS> http://[LB_IP]/-> 200 OK (JSON Data) - Direct Access:
curl https://[RUN_URL].run.app-> 403 Forbidden (Access Denied) - SQL Injection:
curl "http://[LB_IP]/?id=1' OR 1=1"-> 403 Forbidden (Blocked by WAF)
📚 Detailed Documentation
For a deep dive into the technical specifics, including architecture diagrams, line-by-line code explanations, and script breakdowns, visit the detailed documentation in the repository:
👉 Technical Documentation Table of Contents 👉 View the full source code on GitHub
🏁 Conclusion
You have successfully built a secure, scalable microservice architecture on GCP. This isn’t just a demo; it’s a production-ready pattern you can adapt for your own applications.
🔮 What’s Next?
In Part 2, we will add distributed caching with Cloud Memorystore (Valkey) and evolve our Basic Auth into a robust JWT-based Authentication system. Stay tuned!
Published on nja.dev — January 2026
