Lesson 08 of 18 · Part 2 — Build the platform
Artifact Registry & application delivery
Get images to GKE safely and deploy them: Artifact Registry repositories, remote repositories as a pull-through cache, scanning and cleanup, image streaming for fast starts, CI that authenticates without keys, and a rollout that doesn't drop requests.
Artifact Registry
Image paths look like REGION-docker.pkg.dev/PROJECT/REPOSITORY/IMAGE:TAG (Container Registry's gcr.io paths are now served by Artifact Registry too).
A common layout, in a shared platform project:
| Repository | Type | Holds |
|---|---|---|
apps |
Standard | Your application images, written only by CI |
base |
Standard | Approved base images |
dockerhub |
Remote | Pull-through cache of Docker Hub |
helm |
Standard (OCI) | Helm charts |
- Access: CI identities get
artifactregistry.writeronapps; node service accounts (or workload identities) of each cluster getartifactregistry.reader. Keep repositories in the same region as the clusters to avoid cross-region costs and latency. - Cleanup policies delete old untagged images and keep the last N versions.
- Vulnerability scanning (Artifact Analysis) scans images on push and keeps re-checking them against new advisories; findings feed the security posture views (lesson 12).
- Immutable tags on release repositories prevent re-pushing an existing tag.
Artifact Registry is your own warehouse. Instead of every truck driving to a far-away supplier (Docker Hub) each morning, the warehouse keeps copies (remote repository), checks every box for damage (scanning) and throws out old stock (cleanup).
Image streaming
With image streaming enabled on the cluster, nodes start containers before the full image is pulled, reading layers on demand from Artifact Registry. It noticeably speeds up scale-out of large images. It works for images in Artifact Registry (check the current requirements for your setup).
CI without keys
A GitHub Actions job that builds and pushes with Workload Identity Federation:
permissions:
contents: read
id-token: write # lets the job request an OIDC token
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/github
service_account: ci-push@platform-shared.iam.gserviceaccount.com
- uses: google-github-actions/setup-gcloud@v2
- run: gcloud auth configure-docker europe-west1-docker.pkg.dev --quiet
- run: |
IMAGE=europe-west1-docker.pkg.dev/platform-shared/apps/shop:${GITHUB_SHA::7}
docker build -t "$IMAGE" .
docker push "$IMAGE"
Restrict the provider with an attribute condition (for example only assertion.repository == 'my-org/shop'), so other repositories can't get the credentials. The GitHub Actions track covers OIDC in depth. Cloud Build is the Google-native alternative and authenticates with its own service account.
Deploying
Best practice is GitOps: CI pushes the image and updates the tag in a deploy repository; Argo CD or Config Sync applies it (lesson 14, and the Argo CD — Level by Level track). Cloud Deploy is Google's managed delivery pipeline (promotion across targets, approvals, canary) if you prefer a managed service.
A Deployment that rolls out without dropping requests:
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop
namespace: shop
spec:
replicas: 3
selector:
matchLabels: { app: shop }
strategy:
rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
template:
metadata:
labels: { app: shop }
spec:
serviceAccountName: shop
terminationGracePeriodSeconds: 40
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: { app: shop }
containers:
- name: shop
image: europe-west1-docker.pkg.dev/platform-shared/apps/shop:1.4.2
ports: [ { containerPort: 8080 } ]
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { memory: 256Mi }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
lifecycle:
preStop:
exec: { command: [ "sleep", "10" ] }
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: shop
namespace: shop
spec:
minAvailable: 2
selector:
matchLabels: { app: shop }
- Requests decide scheduling, autoscaling and, in Autopilot, cost.
- Zone spread keeps a zone failure from taking all replicas.
- readinessProbe + preStop sleep keep the load balancer's NEG in step during rollouts.
- The PDB protects availability during node upgrades and autoscaler scale-down.
Try it: build, push, deploy (lab project)
- Create an Artifact Registry repository
appsand a remote repositorydockerhubin your region. - Set up a Workload Identity pool and provider for GitHub, restricted to your repository; run the workflow above.
- Grant the lab cluster's node service account reader access and deploy the image with the manifests above.
- Pull
nginxthrough the remote repository (europe-west1-docker.pkg.dev/<project>/dockerhub/library/nginx:alpine) from a private-node cluster. - Change the image tag and watch
kubectl rollout status; run a curl loop through the Gateway from lesson 07 and confirm no errors.
Going deeper: trust what you run
- Sign images in CI and enforce signatures at admission with Binary Authorization or a policy engine (lesson 12).
- Pin images by digest in production manifests (
@sha256:…), with the tag kept as a comment for humans. - Use VPC Service Controls around Artifact Registry when data-exfiltration rules apply, and test CI access through the perimeter.
Recap
- Artifact Registry: regional repositories, IAM per repository, remote repositories as caches, cleanup, scanning.
- Image streaming speeds up start-up of large images.
- CI authenticates with Workload Identity Federation, never with JSON keys.
- Deploy through GitOps (or Cloud Deploy), with requests, zone spread, readiness, preStop and PDBs.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.