Exam objective 1.1 — Containerize applications and automate builds using Cloud Build — solves the classic 'it works on my machine' problem. As a PCD candidate, you must understand how to package an application with everything it needs to run, and then automate that packaging process so it happens reliably and consistently. This chapter makes you the person who knows how to send a perfect, portable application to any computer, every single time.
Jump to a section
A simple way to picture Containerizing Apps with Docker and Cloud Build
A professional meal prep chef takes a detailed recipe and all the fresh ingredients for a gourmet dish—say, a delicate miso-glazed salmon with roasted vegetables and a citrus beurre blanc.
The chef follows the recipe step-by-step to prepare the full meal, then carefully seals it inside a single, airtight container. Every component—the salmon, the vegetables, the sauce—is packed together so anyone can reheat and eat the exact same meal later. The container holds everything the dish needs to run: the cooked food, the correct reheating instructions, and the precise plating order.
Now imagine that chef has to prepare the same meal for a hundred different people. Doing it manually for each person would be slow and inconsistent. So they create a standardised 'recipe box' that includes the sealed container of food plus a set of written instructions. They give this template to an automated kitchen assistant. The assistant receives the recipe box, reads the instructions, and repeatedly produces the exact same sealed meal container, over and over, without any variation.
In this analogy, the meal prep chef is a software developer who takes application code and all its dependencies—libraries, frameworks, and settings—and bundles them into a portable, sealed unit called a container image. The Dockerfile is the written recipe that describes exactly how to build the image. The automated kitchen assistant is Cloud Build, a service that reads the Dockerfile recipe and automatically creates the container image every time the chef pushes an update. Just as the meal container travels safely to any kitchen, the containerised app runs identically on a laptop, a test server, or in the cloud.
Before containers, deploying a software application was a fragile, manual process. A developer would write code on their laptop, where the operating system had a specific version of Python, a certain library for handling images, and a particular database driver. The app ran perfectly. To share that app, the developer would send the code file to a colleague or to a server. But that colleague's computer might have Python version 3.9 instead of 3.7. It might be missing the image library entirely. The database driver might be incompatible. The app would crash immediately with confusing errors. This is the infamous 'it works on my machine' problem.
Containers solve this by packaging the application code together with its entire runtime environment. Think of a container as a standardised, lightweight, standalone, executable package. It bundles the application's source code, the operating system-level dependencies (system tools, libraries, and settings), configuration files, and any other files the application needs to run. The key technology that manages containers is Docker. Docker provides the tools and the platform to create, deploy, and run containers.
The central component of Docker is an image. A Docker image is a read-only template that contains the instructions for creating a container. It is like a snapshot of the application and its environment. When you run an image, Docker creates a writable layer on top of it, and that running instance is called a container. You can have multiple containers running from the same image. The image itself never changes.
To build an image, you write a Dockerfile. A Dockerfile is a plain-text document that contains a series of step-by-step instructions. Each instruction adds a new layer to the image. The typical Dockerfile starts with a base image, which is an official, pre-configured operating system or language runtime (like 'python:3.10-slim'). Then you add commands to install software, copy your application code, set environment variables, and define the command that should run when the container starts. The Dockerfile is the recipe for your container.
Here is a simplified example of a Dockerfile for a Python web application:
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]This Dockerfile reads like a set of instructions:
FROM: Start with the official Python 3.10 base image.
WORKDIR: Create and move into a folder called /app inside the container.
COPY: Copy the requirements.txt file from your local project into the container.
RUN: Execute the pip command to install the Python libraries listed in requirements.txt.
COPY: Copy the rest of your application code into the container.
CMD: Define the default command to run when the container starts.
Building this Dockerfile creates a Docker image. You can test this image locally on your development machine. It will run identically anywhere Docker is installed because the container carries its own full environment.
Now, let us introduce Cloud Build. Cloud Build is Google Cloud's managed service for continuously building, testing, and deploying software. It takes the human error out of building containers. Instead of a developer manually running 'docker build' on their laptop, Cloud Build listens for a trigger—like a new code commit pushed to a repository in Cloud Source Repositories, GitHub, or Bitbucket. When the trigger fires, Cloud Build pulls the code, reads the Dockerfile (or a custom build configuration file called cloudbuild.yaml), and automatically runs the build steps. It produces the Docker image and pushes it to a registry (like Artifact Registry) where it can be deployed.
The key concepts for the PCD exam regarding automation with Cloud Build are:
Build Triggers: You configure a trigger that associates a repository and a branch (for example, the 'main' branch of your GitHub repo) with a build configuration. Every time a new commit is pushed to that branch, Cloud Build automatically starts a build.
Cloud Build Configuration File (cloudbuild.yaml): This YAML file defines the steps of your build. A step might say 'build this Docker image' or 'run unit tests' or 'push the image to Artifact Registry'. It is the recipe for the automation pipeline.
Artifact Registry: A secure, private registry for storing your container images. Once Cloud Build builds the image, it stores it here.
IAM Permissions: Cloud Build needs permissions to access your source code repository and to push images to Artifact Registry. You configure these using Google Cloud's Identity and Access Management (IAM).
Why does this matter for Cloud Developers? In a real-world company, multiple developers are pushing code changes dozens of times a day. Manually building each change into a container is impossible. Cloud Build automates that process, ensuring every build is identical and reproducible. It enforces consistency and frees developers to focus on writing code rather than managing build environments.
Write the Dockerfile
Create a file named 'Dockerfile' in your project's root directory. Define the base image (FROM), set the working directory (WORKDIR), install dependencies (RUN), copy your source code (COPY), and specify the startup command (CMD or ENTRYPOINT). This file is the recipe that tells Docker how to build your application's container.
Build and test the image locally
Run 'docker build -t my-app:latest .' in your terminal. This reads the Dockerfile and produces a Docker image tagged 'my-app:latest'. Then run 'docker run -p 8080:80 my-app:latest' to start a container from that image and test your application works correctly on your development machine.
Push the code and Dockerfile to a repository
Commit your Dockerfile and application code to a version-controlled repository such as GitHub, Bitbucket, or Cloud Source Repositories. This step makes the source code accessible to Cloud Build and enables version tracking for every build.
Create a build trigger in Cloud Build
In the Google Cloud Console, navigate to Cloud Build and create a new trigger. Connect it to your repository, select the branch (e.g., '^main$'), and specify the build configuration file (cloudbuild.yaml). This trigger instructs Cloud Build to run an automated build every time new code is pushed to that branch.
Write the cloudbuild.yaml configuration file
Create a YAML file named 'cloudbuild.yaml' in your repository root. Define steps such as building the Docker image with a unique tag (e.g., using $SHORT_SHA) and pushing it to Artifact Registry. This file tells Cloud Build exactly what tasks to execute during the build.
Verify the automated build
Push a small change (like a comment or a README update) to the configured branch. Go to Cloud Build in the console and watch the build history. You will see a new build triggered automatically. Once it succeeds, check Artifact Registry to confirm the new container image is stored with a tag matching your commit hash.
Imagine you work as a Cloud Developer for an e-commerce company called 'ShopStream'. Your team builds the product catalogue microservice—a small application that retrieves product details from a database and displays them on the website. The application is written in Node.js.
Step 1: You write a Dockerfile for the microservice. It starts with 'FROM node:18-alpine', installs the Node.js dependencies listed in a 'package.json' file, copies your application source code, and sets the startup command to 'node server.js'. You test this Dockerfile on your local machine by building the image with 'docker build -t shopstream/catalogue:latest .' and running it in a container with 'docker run -p 3000:3000 shopstream/catalogue:latest'. It works.
Step 2: You push your code (including the Dockerfile) to a GitHub repository. You then log in to Google Cloud Console and navigate to Cloud Build. You create a new build trigger:
Name: catalogue-trigger
Event: Push to a branch
Source: GitHub, linked to your repository
Branch: ^main$
Build Configuration: Cloud Build configuration file (cloudbuild.yaml)
Step 3: You write a simple cloudbuild.yaml file in your repository's root directory:
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'us-central1-docker.pkg.dev/my-project/shopstream/catalogue:$SHORT_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'us-central1-docker.pkg.dev/my-project/shopstream/catalogue:$SHORT_SHA']This cloudbuild.yaml has two steps: 1. Build the Docker image, tagging it with a unique identifier ($SHORT_SHA is the short commit hash) and storing it in your Artifact Registry repository in the us-central1 region. 2. Push (upload) that image to Artifact Registry.
Step 4: A senior developer merges a bug fix into the main branch on GitHub. Instantly, Cloud Build detects the push event. It checks out the latest code, reads your cloudbuild.yaml, and executes the steps. Within a few minutes, a new container image tagged with that specific commit hash is stored in Artifact Registry.
Step 5: You use that image to deploy the updated microservice to a Kubernetes cluster or to Cloud Run. You can always trace exactly which version of the code is running because every build is tagged with its commit hash. If the deployment fails, you can roll back by pointing the deployment to a previous image tag.
What your day-to-day looks like:
You never manually run 'docker build' for production images again.
You write and maintain Dockerfiles and cloudbuild.yaml files.
You review build logs in Cloud Build when a build fails (e.g., a dependency is missing).
You manage IAM permissions so Cloud Build can access the repository and Artifact Registry.
You set up notifications (via Pub/Sub) so the team gets a Slack message when a build succeeds or fails.
This automation means your team can deploy changes reliably, multiple times per day, without human errors. It also means that if a security vulnerability is found in the base Node.js image, you update the base image version in the Dockerfile, push the change, and Cloud Build immediately rebuilds and re-deploys.
The Google Professional Cloud Developer (PCD) exam tests objective 1.1 — Containerize applications and automate builds using Cloud Build — in several specific ways. You must be precise about definitions, services, and configuration syntax.
First, the exam will question the difference between a Docker image and a Docker container. The trap they set is asking which is the 'running instance' versus the 'read-only template'. Remember: an image is the static, immutable snapshot. A container is the running, writable instance of that image. Multiple containers can run from the same image.
Second, they test your understanding of the Dockerfile syntax. You will likely see a Dockerfile snippet and be asked to identify what a specific instruction does. The most commonly tested instructions are:
FROM: Specifies the base image. Always required and must be the first instruction.
COPY vs ADD: Both copy files into the image. ADD can also handle remote URLs and automatically extract tar archives. The exam expects COPY for local files because COPY is more explicit and transparent.
RUN: Executes commands in a new layer on top of the current image during the build.
CMD vs ENTRYPOINT: CMD provides defaults for an executing container. It can be overridden when you run the container. ENTRYPOINT configures the container to run as an executable. If you combine them, ENTRYPOINT is the command and CMD are its default arguments.
EXPOSE: Documents which port the container listens on. It does not actually publish the port.
WORKDIR: Sets the working directory for subsequent instructions.
Third, they test Cloud Build configuration. Expect a question that shows a cloudbuild.yaml file and asks what the build output will be. The key sections are:
'steps': An ordered list of build steps. Each step runs in a separate container.
'name': The Docker image used for that step (e.g., 'gcr.io/cloud-builders/docker' for Docker commands).
'args': The arguments passed to that container's entrypoint.
'images': Optional field to specify images to push to Artifact Registry.
'substitutions': Built-in variables like $PROJECT_ID, $BUILD_ID, $SHORT_SHA. They love testing which substitution variable is appropriate for tagging an image with a commit hash. That is $SHORT_SHA.
Fourth, they test the sequence of a Cloud Build trigger. The correct order is: 1. A developer pushes code to a repository. 2. The build trigger fires. 3. Cloud Build clones the repository and executes the build steps. 4. Cloud Build pushes the resulting container image to Artifact Registry. 5. The image is stored and can be deployed.
The trap here is confusing Artifact Registry with Container Registry. Artifact Registry is the modern, recommended service. Container Registry is legacy and deprecated. The exam expects you to recognise Artifact Registry as the correct answer.
Fifth, they test IAM roles for Cloud Build. You need to know two specific roles:
Cloud Build Editor (roles/cloudbuild.builds.editor): Allows users to submit builds.
Cloud Build Service Agent (roles/cloudbuild.serviceAgent): Grants Cloud Build service account access to other services (like Artifact Registry).
Finally, remember that Cloud Build can also be used for non-container builds (e.g., compiling a Java app into a JAR file). But for this objective, focus on container builds. The correct answer pattern usually involves selecting the service that automates builds 'triggered by code changes' (Cloud Build) combined with the storage location (Artifact Registry) using a configuration file (cloudbuild.yaml).
A Docker image is the read-only blueprint; a container is the running instance of that image.
A Dockerfile is a plain-text script that defines every layer and instruction to build a Docker image.
Cloud Build is a fully managed CI/CD service that automatically runs builds when triggered by code pushes to a repository.
The cloudbuild.yaml file defines the custom build steps, including which Docker commands to run and where to push the resulting image.
Artifact Registry is the recommended service for storing and managing container images on Google Cloud.
Always tag your container images with a unique identifier like a Git commit hash ($SHORT_SHA) to enable traceable deployments and rollbacks.
Build triggers in Cloud Watch for events on specific branches, allowing you to run different pipelines for development, staging, and production.
Every build step in cloudbuild.yaml runs inside a separate container, which means you can use any tool by specifying its Docker image as the step's 'name'.
These come up on the exam all the time. Here's how to tell them apart.
Docker Image
Read-only template that never changes after creation
Can be stored in a repository like Artifact Registry
Created by running 'docker build' on a Dockerfile
Docker Container
Running, writable instance of an image
Can be started, stopped, and deleted
Created by running 'docker run' on an image
Dockerfile
Defines how to build a single container image
Uses instructions like FROM, RUN, COPY, CMD
Processed by Docker engine during 'docker build'
cloudbuild.yaml
Defines the entire build pipeline, possibly with multiple steps
Can run linting, unit tests, multiple Docker builds, and deployments
Processed by Cloud Build during an automated build
Artifact Registry
Recommended by Google for storing container images
Supports multiple formats (Maven, npm, Python packages besides Docker images)
Region-specific repository names (e.g., us-central1-docker.pkg.dev/...)
Container Registry (legacy)
Legacy service, still present but deprecated
Only supports Docker images
Repository names use a global hostname (gcr.io/...)
COPY instruction
Copies files and directories from the build context into the image
Does not handle remote URLs or automatic archive extraction
Preferred for local file copying per Docker best practice
ADD instruction
Copies files and directories from the build context into the image
Can retrieve files from remote URLs
Automatically extracts tar archives (tar, gzip, bzip2)
CMD instruction
Provides default command and arguments for the container
Can be overridden entirely when running the container
Used alone or as default arguments for ENTRYPOINT
ENTRYPOINT instruction
Configures the container to run as an executable
Harder to override (requires --entrypoint flag)
Used when you want the container to behave like a binary program
Mistake
A Docker image and a Docker container are the same thing; the terms are interchangeable.
Correct
A Docker image is a read-only, static template. A Docker container is a running, writable instance of that image. You can start, stop, and delete containers, but images remain unchanged.
The two terms are often used loosely in casual conversation, and the metaphor of 'a container' can imply the whole package rather than its running state.
Mistake
Cloud Build only works with Dockerfiles—you cannot customise the build steps.
Correct
Cloud Build can use a cloudbuild.yaml file that defines custom steps. Each step runs in a separate container image you specify, allowing complex pipelines (e.g., running tests, linting, SonarQube analysis) beyond just building a Docker image.
Newcomers see the common 'docker build' use case and assume Cloud Build is a thin wrapper around Docker, missing its flexibility as a general-purpose CI/CD platform.
Mistake
Once a container image is built, you never need to rebuild it. It runs forever as-is.
Correct
Container images must be rebuilt whenever the source code, its dependencies, or the base image changes. Security patches, bug fixes, and feature updates all trigger new builds and new image tags.
The 'immutable' nature of images leads beginners to think they are permanent, ignoring the reality that applications evolve continuously and base images frequently contain security vulnerabilities.
Mistake
You can only use Cloud Build with Google Cloud's own source repositories like Cloud Source Repositories.
Correct
Cloud Build natively supports triggers from GitHub, Bitbucket, and GitLab (via third-party connections), in addition to Cloud Source Repositories. The exam tests that you can connect external repositories.
Beginners often assume a Google Cloud service only works with other Google Cloud services, underestimating the multi-platform integration capabilities.
Mistake
The 'EXPOSE' instruction in a Dockerfile actually makes your container accessible on that port from the internet.
Correct
EXPOSE is purely documentation. It tells anyone reading the Dockerfile which port the application inside the container listens on. The port is only accessible when you run the container with the '-p' flag (e.g., 'docker run -p 8080:80').
The name 'EXPOSE' sounds like an active action, leading beginners to believe it configures networking, when it is actually a passive metadata declaration.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
A Docker image is a read-only template that contains everything needed to run an application. A Docker container is a running instance created from that image. You can have many containers running from the same image simultaneously.
No. Cloud Build is a managed service that runs on Google's infrastructure. You only need a local Docker installation if you want to test your container before pushing code to the repository. Cloud Build handles building and pushing images automatically.
The EXPOSE instruction documents which port the application inside the container listens on. It does not publish the port or make it accessible from outside. You must use the '-p' flag when running the container to actually expose the port.
In your cloudbuild.yaml, when using the Docker builder step, set the 'args' to include '-f' followed by the path to your Dockerfile (e.g., '-f', 'path/to/my.Dockerfile'). The default is 'Dockerfile' in the root directory.
Check the IAM roles for Cloud Build's service account. The service account needs permissions to read the source repository (e.g., roles/cloudbuild.builds.editor) and to push images to Artifact Registry (roles/artifactregistry.writer). You can assign these in the IAM section of Google Cloud Console.
Yes. You can add steps to your cloudbuild.yaml that deploy the image to Cloud Run, Google Kubernetes Engine, or Compute Engine. For example, use the 'gcloud' builder step to run a deployment command after the image is pushed to the registry.
You've finished Containerizing Apps with Docker and Cloud Build. Continue through the PCD study guide to build a complete picture of the exam.
Done with this chapter?