Mastering Docker Deployment on Google Cloud: Guide to Modern Container Delivery
Containerizing an application is routine; production-grade deployment is not. Whether scaling APIs, batch jobs, or microservices, Google Cloud’s managed platforms—GKE (Kubernetes Engine) and Cloud Run—streamline orchestration and delivery, shifting focus from infrastructure firefighting to application evolution. Below, deploy a Node.js service via Docker onto both GKE and Cloud Run, with attention to pitfalls, security boundaries, and practical trade-offs.
Preliminaries
- GCP project with billing enabled
gcloud
CLI: v470+ recommended- Docker: v20.10+
- Basic understanding of Dockerfile and image tagging
Authentication:
gcloud auth login
gcloud config set project [PROJECT-ID]
Note: Permissions must allow compute, container, and artifact registry management.
Containerize: Node.js Service Example
No “hello world”—instead, expose a realistic health check and log payloads.
app.js:
const express = require('express');
const app = express();
app.use(express.json());
app.get('/healthz', (req, res) => res.send('ok'));
app.post('/process', (req, res) => {
console.info('Request payload:', req.body);
res.json({ processed: true });
});
const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening at :${port}`));
Dockerfile:
FROM node:14.21-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \
CMD wget --spider -q http://localhost:8080/healthz || exit 1
CMD ["node", "app.js"]
Build the image:
docker build -t gcr.io/[PROJECT-ID]/sample-app:2024-06 .
Tip: Tag with explicit version or date. Avoid latest
in shared environments.
Push to Artifact or Container Registry
Authenticate Docker:
gcloud auth configure-docker gcr.io
Push:
docker push gcr.io/[PROJECT-ID]/sample-app:2024-06
Known issue: Artifact Registry (recommended over legacy GCR) uses a different hostname.
Option 1: Deploy to GKE (Google Kubernetes Engine)
Minimalist Cluster Creation with Autoscaling
Provision a regionally resilient cluster with node autoscaling.
gcloud container clusters create-auto sample-cluster \
--region us-central1 \
--release-channel=regular \
--project=[PROJECT-ID]
Fetch credentials for kubectl
:
gcloud container clusters get-credentials sample-cluster --region us-central1
Kubernetes Manifest: Deployment + Service
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-app
spec:
replicas: 2
selector:
matchLabels: { app: sample-app }
template:
metadata:
labels: { app: sample-app }
spec:
containers:
- name: app
image: gcr.io/[PROJECT-ID]/sample-app:2024-06
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
service.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-app
spec:
selector: { app: sample-app }
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
Apply both:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Check external IP:
kubectl get svc sample-app --watch
Expect up to several minutes for allocation—Classic LoadBalancer provisioning often stalls when quotas are low.
Horizontal Pod Autoscaling
Enable real autoscaling based on CPU usage:
kubectl autoscale deployment sample-app --cpu-percent=60 --min=2 --max=7
Scaling granularity depends on how metrics-server is installed. Lacking metrics leads to errors like:
unable to fetch metrics from resource metrics API: the server is currently unable to handle the request (get pods.metrics.k8s.io)
Practical note: For production, always pin the cluster version and explicitly manage node pools for cost and reliability. Avoid ephemeral clusters for stateful workloads.
Option 2: Deploy to Cloud Run
Cloud Run abstracts away nodes, pods, and service wiring. For stateless HTTP workloads, it’s the default for many teams—latency may suffer on “cold starts,” but config options can mitigate.
Deploy from the pushed image:
gcloud run deploy sample-app-cr \
--image gcr.io/[PROJECT-ID]/sample-app:2024-06 \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--memory=512Mi --cpu=1 --min-instances=0
Tip: Adjust --min-instances
to keep warm containers; increases cost but reduces startup lag. Security-sensitive APIs should default to --no-allow-unauthenticated
and integrate IAM.
Retrieve URL via terminal output or:
gcloud run services describe sample-app-cr --platform managed --region us-central1 --format="value(status.url)"
Side note: Cloud Run automatically handles HTTPS, rollout, and log integration with Cloud Logging.
Hard-Won Lessons & Non-Obvious Notes
Issue | Platform | Mitigation / Note |
---|---|---|
IAM over-permission | Both | Use workload identity; avoid granting Owner/Editor |
Ingress IP stuck as pending | GKE | Check subnet/firewall/quotas; sometimes invisible bug |
Container cold starts | Cloud Run | Tune --min-instances , design idempotent startup |
Log overload | Both | Use structured logs, log sampling for high volume |
Resource overcommit | GKE | Tune requests/limits — GKE will evict or throttle |
Pro tip: Automate push/build/deploy with Cloud Build triggers or third-party CI runners (e.g., GitHub Actions). Embed gitsha
in tag for traceability.
Wrapping Up
Cloud deployment is less about putting an image somewhere, more about lifecycle: scaling, cost, and diagnosis. GKE fits high-traffic, multi-service, or hybrid apps; Cloud Run excels at event-driven APIs. Both profit from explicit resource, identity, and cost management—shortcuts multiply pain later.
Alternative exists: Composer for Airflow; App Engine for legacy runtime support—select tools fit other profiles, but container-first models are dominant for greenfield builds.
References
- GKE Documentation
- Cloud Run Docs
- Artifact Registry Setup
- github: GoogleCloudPlatform/cloud-builders-community
Questions, edge case notes, or run-into-a-wall stories? Add them below. The best technique is often the one learned right after production blows up.