This walkthrough shows a solid, production-style setup for a Spring Boot application running on Google Cloud Platform, fronted by a Google Cloud HTTP(S) Load Balancer with TLS, health checks, autoscaling, and clean routing.
I’ll cover two common deployment paths:
-
Path A (recommended for many Spring Boot teams): Compute Engine Managed Instance Group (MIG) + External HTTP(S) Load Balancer
-
Path B (container-first): GKE / Cloud Run (quick notes at the end)
What you’ll build
Users → Global external HTTP(S) Load Balancer → Backend service → MIG (Spring Boot VMs)
Key pieces:
-
A Spring Boot service listening on a known port (e.g.,
8080) -
A health endpoint that returns
200 OK(e.g.,/actuator/health) -
A Managed Instance Group (for scale + self-heal)
-
A Backend service with a health check
-
A URL map + target proxy + forwarding rule
-
Optional: Managed SSL certificate + Cloud DNS
Prereqs
-
A GCP project with billing enabled
-
gcloudinstalled and authenticated -
A domain name (optional but recommended for HTTPS with managed cert)
-
Spring Boot app ready to run in production profile
Set your defaults:
gcloud config set project YOUR_PROJECT_ID
gcloud config set compute/region asia-south1
gcloud config set compute/zone asia-south1-a
Step 1: Prepare your Spring Boot app for load balancing
1.1 Add a health endpoint
If you use Spring Actuator:
Gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
application.yml
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
probes:
enabled: true
Health endpoint:
-
/actuator/health(or/actuator/health/livenessdepending on config)
1.2 Make sure your app binds correctly
Ensure Spring Boot binds to all interfaces:
server:
address: 0.0.0.0
port: 8080
1.3 Keep it stateless
A load balancer will route requests across instances. Prefer:
-
external session store (Redis / Cloud Memorystore), or
-
JWT/stateless auth
Step 2: Build a VM image that runs your Spring Boot app
You have two practical approaches:
Option A: Startup script on a base OS (simple)
-
Create an instance template that installs Java and runs the jar via a startup script.
Option B: Bake a custom image (cleaner, faster scale-up)
-
Use Packer or custom image pipeline.
Below is Option A (fast to implement).
Step 3: Create an instance template (with startup script)
3.1 Upload your app artifact
Example: put the jar in a GCS bucket:
gsutil mb -l asia-south1 gs://YOUR_BUCKET_NAME
gsutil cp build/libs/your-app.jar gs://YOUR_BUCKET_NAME/
3.2 Create a service account for instances (recommended)
gcloud iam service-accounts create springboot-vm-sa \
--display-name="Spring Boot VM Service Account"
Grant only what you need (example: read jar from GCS):
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:springboot-vm-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
3.3 Create a startup script
Create startup.sh locally:
cat > startup.sh <<'EOF'
#!/bin/bash
set -e
APP_BUCKET="YOUR_BUCKET_NAME"
APP_JAR="your-app.jar"
APP_DIR="/opt/app"
PORT="8080"
apt-get update
apt-get install -y default-jre-headless google-cloud-cli
mkdir -p ${APP_DIR}
gsutil cp gs://${APP_BUCKET}/${APP_JAR} ${APP_DIR}/${APP_JAR}
cat > /etc/systemd/system/springboot.service <<SYSTEMD
[Unit]
Description=Spring Boot App
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=${APP_DIR}
ExecStart=/usr/bin/java -jar ${APP_DIR}/${APP_JAR} --server.port=${PORT}
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
SYSTEMD
systemctl daemon-reload
systemctl enable springboot.service
systemctl start springboot.service
EOF
3.4 Create the instance template
gcloud compute instance-templates create springboot-template \
--machine-type=e2-medium \
--service-account=springboot-vm-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--tags=springboot-backend \
--metadata-from-file=startup-script=startup.sh \
--image-family=debian-12 \
--image-project=debian-cloud
Step 4: Create a Managed Instance Group (MIG)
gcloud compute instance-groups managed create springboot-mig \
--base-instance-name=springboot \
--size=2 \
--template=springboot-template
Enable autoscaling (example):
gcloud compute instance-groups managed set-autoscaling springboot-mig \
--max-num-replicas=10 \
--min-num-replicas=2 \
--target-cpu-utilization=0.6 \
--cool-down-period=60
Step 5: Allow traffic from the load balancer to your instances (Firewall)
For an external HTTP(S) Load Balancer, backend VMs must allow traffic from Google LB health check + proxy ranges.
Create a firewall rule allowing traffic to port 8080 from Google LB ranges:
gcloud compute firewall-rules create allow-lb-to-springboot \
--network=default \
--action=ALLOW \
--direction=INGRESS \
--rules=tcp:8080 \
--source-ranges=130.211.0.0/22,35.191.0.0/16 \
--target-tags=springboot-backend
If you use a separate health check path, same port is fine.
Step 6: Create a health check for the backend
Use HTTP health check to /actuator/health:
gcloud compute health-checks create http springboot-hc \
--port 8080 \
--request-path /actuator/health \
--check-interval 10s \
--timeout 5s \
--unhealthy-threshold 3 \
--healthy-threshold 2
Step 7: Create a backend service and attach the MIG
7.1 Create backend service
gcloud compute backend-services create springboot-backend \
--protocol=HTTP \
--port-name=http \
--health-checks=springboot-hc \
--global
7.2 Attach the MIG
First, make the MIG a backend (needs an instance group reference; MIG is zonal by default):
gcloud compute backend-services add-backend springboot-backend \
--instance-group=springboot-mig \
--instance-group-zone=asia-south1-a \
--global
Step 8: Create URL map (routing rules)
Basic single-service routing:
gcloud compute url-maps create springboot-urlmap \
--default-service springboot-backend
If later you want /api/* to one backend and /static/* to another, you’d add path matchers.
Step 9: Create the target HTTP proxy + forwarding rule (HTTP)
9.1 Target HTTP proxy
gcloud compute target-http-proxies create springboot-http-proxy \
--url-map=springboot-urlmap
9.2 Global forwarding rule (port 80)
gcloud compute forwarding-rules create springboot-http-fr \
--global \
--target-http-proxy=springboot-http-proxy \
--ports=80
Get the LB IP:
gcloud compute forwarding-rules describe springboot-http-fr --global --format="value(IPAddress)"
Test:
curl -i http://LB_IP/
curl -i http://LB_IP/actuator/health
At this point you have a working HTTP load balancer.
Step 10: Enable HTTPS with a managed certificate (recommended)
10.1 Reserve a static global IP (best practice)
gcloud compute addresses create springboot-lb-ip --global
gcloud compute addresses describe springboot-lb-ip --global --format="value(address)"
Re-create the forwarding rule to use this IP (or create a new one):
gcloud compute forwarding-rules delete springboot-http-fr --global -q
gcloud compute forwarding-rules create springboot-http-fr \
--global \
--address=springboot-lb-ip \
--target-http-proxy=springboot-http-proxy \
--ports=80
10.2 Create a managed SSL certificate
gcloud compute ssl-certificates create springboot-managed-cert \
--domains=yourdomain.com \
--global
Managed cert becomes ACTIVE only after DNS points to the LB IP.
10.3 Create an HTTPS target proxy
gcloud compute target-https-proxies create springboot-https-proxy \
--url-map=springboot-urlmap \
--ssl-certificates=springboot-managed-cert
10.4 Create HTTPS forwarding rule (port 443)
gcloud compute forwarding-rules create springboot-https-fr \
--global \
--address=springboot-lb-ip \
--target-https-proxy=springboot-https-proxy \
--ports=443
Step 11: Point DNS to the load balancer
In your DNS provider (or Cloud DNS), create:
-
Arecord:yourdomain.com→LB_STATIC_IP
Once propagated, check cert status:
gcloud compute ssl-certificates describe springboot-managed-cert --global
When it’s ACTIVE:
curl -i https://yourdomain.com/actuator/health
Step 12: (Strongly recommended) Force HTTP → HTTPS redirect
Create a second URL map just for redirects:
gcloud compute url-maps create springboot-redirect-map \
--default-url-redirect=httpsRedirect=true,responseCode=301
Create a redirect proxy and update the HTTP forwarding rule:
gcloud compute target-http-proxies create springboot-redirect-proxy \
--url-map=springboot-redirect-map
gcloud compute forwarding-rules delete springboot-http-fr --global -q
gcloud compute forwarding-rules create springboot-http-fr \
--global \
--address=springboot-lb-ip \
--target-http-proxy=springboot-redirect-proxy \
--ports=80
Now all http:// gets redirected to https://.
Step 13: Observability & operations checklist
Logging and metrics
-
Enable Cloud Logging and Cloud Monitoring (default on GCE)
-
Add Spring Boot structured logs (JSON) if you can
-
Consider exporting app metrics using Micrometer to Cloud Monitoring or Prometheus (if on GKE)
Security hardening
-
Put instances in private subnets (if using Shared VPC) and only allow LB ingress
-
Use least privilege for instance service account
-
Use Secret Manager for secrets (don’t bake into VM)
Reliability
-
Use regional MIG for higher availability across zones (recommended for prod)
-
Enable autohealing on MIG using the same health check:
gcloud compute instance-groups managed set-autohealing springboot-mig \
--health-check=springboot-hc \
--initial-delay=120
Common Spring Boot gotchas behind a load balancer
-
If you generate absolute URLs or redirects, configure forwarded headers:
-
In Spring Boot, ensure it respects
X-Forwarded-*headers (depends on version and config).
-
-
If you have large uploads, tune max request size and timeouts.
-
Health endpoint must be fast and consistently return
200.
Alternative: If your Spring Boot app is containerized
Cloud Run
-
Easiest: deploy to Cloud Run and optionally put it behind an external HTTPS LB for custom domains / advanced routing / WAF.
-
Cloud Run already scales and handles many LB-ish concerns.
GKE Ingress
-
You’d create a Kubernetes
Service+Ingress(or Gateway API), and GKE provisions the LB.
If you tell me which runtime you’re actually using (GCE VM, GKE, or Cloud Run) and whether you need internal or external LB, I’ll tailor the article to that exact architecture and include the right commands and diagrams.
No comments:
Post a Comment
Note: only a member of this blog may post a comment.