Docker
Docker builds container images from a Dockerfile: a base image, the steps that add the application, and the user and command it runs with. Every workload image of the GCP platform is built this way in CI, scanned, and pushed once to Artifact Registry.
What it does
A Dockerfile starts FROM a base image and adds layers. A multi-stage build compiles in one stage and copies only the output into a small runtime stage. USER sets the identity the container runs as; a fixed non-root UID lets Kubernetes enforce it. An image is pushed under a tag; a registry with immutable tags never lets that tag point elsewhere.
How BuiltForProd uses it
| Image | Dockerfile | Base | Runs as |
|---|---|---|---|
| Web app API | acme-gcp-blueprint-webapp-code/Dockerfile | python:3.12.15-slim-trixie, Gunicorn on 8080 | 10001:10001 |
| Web app SPA | acme-gcp-blueprint-webapp-code/frontend/Dockerfile | Built on node:22.23.3-alpine3.24, served by nginxinc/nginx-unprivileged:1.30.5-alpine3.24 on 8080 | 101 |
| ETL trigger | acme-gcp-blueprint-etl-code/src/trigger/Dockerfile | python:3.12-slim, Gunicorn with one worker on 8080 | 10001:10001 |
| CI runner | acme-gcp-platform-baseline/runner-image/Dockerfile | ghcr.io/actions/actions-runner:2.337.0, pinned by digest | runner |
| SCC notifier | acme-gcp-platform-baseline/src/scc-notifier/Dockerfile | python:3.13-slim, pinned by digest | 65532:65532 |
Hardening. No image contains a credential: the API reaches Firestore and Valkey with tokens of the pod's identity obtained at run time, the trigger calls Dataproc as its Cloud Run identity, and the Valkey CA arrives as an environment variable. The API's UID 10001 matches the chart's runAsUser, so the Kubernetes restricted policy and the read-only root filesystem hold. The SPA image sends security headers and the cache headers Cloud CDN follows, and has no API address baked in: it derives blueprint-api.<stage>.company.com from its own host, so one image serves every stage.
Build and scan. The code repositories' ci.yml runs on every pull request with no cloud access: lint and tests, then a build of each image and a Trivy scan that fails on fixable CRITICAL or HIGH findings in OS packages and libraries; see Checkov, Trivy and tflint.
Push once. On merge, cd-integration.yml (and cd-frontend.yml for the SPA) pushes the image once as main-<short sha> to its Artifact Registry repository, without provenance or SBOM index so the tag names exactly one image, and skips the build when the tag already exists. A release adds its v* tag to the same image in the registry; nothing is rebuilt between dev and prod. The runner and notifier images are built by the Baseline's runner-image.yml and notifier-image.yml in the same way.
Terms you will see
| Term | Meaning |
|---|---|
| Multi-stage build | Build in one image, copy only the output into the runtime image. |
| Non-root UID | A fixed user ID the container runs as, matched by the pod security settings. |
main-<short sha> | The one tag a build is pushed with. |
| Release tag | A v* tag added to an image that already exists in the registry. |
Where to read more
- GCP Web App Blueprint overview and the GCP Data and ETL Blueprint overview.
- Artifact Registry for where the images live.
- Immutable artifacts for build once, promote everywhere.