Learning Path: Transform a monolith into a GKE app - Containerize the modular app Stay organized with collections Save and categorize content based on your preferences.
This is the fourth tutorial in a learning path that teaches you how tomodularize and containerize a monolithic app.
The learning path consists of the following tutorials:
- Overview
- Understand the monolith
- Modularize the monolith
- Prepare the modular app for containerization
- Containerize the modular app (this tutorial)
- Deploy the app to a GKE cluster
In the previous tutorial,Prepare the modular app for containerization,you saw what changes needed to be made to the modular version of the CymbalBooks app to prepare it for containerization. In this tutorial, you containerizethe app.
Costs
You can complete this tutorial without incurring any charges. However, followingthe steps in thenext tutorial of this series incurs charges on yourGoogle Cloud account. Costs begin when you enable GKE and deploythe Cymbal Books app to a GKE cluster. These costs includeper-cluster charges for GKE, as outlined on thePricingpage, and charges for running Compute Engine VMs.
To avoid unnecessary charges, ensure that you disable GKE ordelete the project once you've completed this tutorial.
Before you begin
Before you begin this tutorial, make sure you completed the earlier tutorialsin the series. For an overview of the whole series, and links to particulartutorials, seeLearning Path: Transform a monolith into a GKE app - Overview.
Set up your environment
In this section, you set up an environment in which you containerize the modularapp. Specifically, you perform the following steps:
- Select or create a Google Cloud project
- Enable the necessary APIs
- Connect Cloud Shell to your Google Cloud project
- Set the default environment variables
- Create a repository in Artifact Registry
- Configure Docker for Artifact Registry
- Get the tutorial code
Select or create a Google Cloud project
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
Note: If you don't plan to keep the resources that you create in this procedure, create a project instead of selecting an existing project. After you finish these steps, you can delete the project, removing all resources associated with the project.Roles required to select or create a project
- Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
- Create a project: To create a project, you need the Project Creator role (
roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.createpermission.Learn how to grant roles.
Verify that billing is enabled for your Google Cloud project.
Enable the necessary APIs
To work with container images and Kubernetes in your Google Cloud project, youneed to enable the following APIs:
- Artifact Registry API: this API enables Artifact Registry, which is a servicefor storing and managing your container images.
- Kubernetes Engine API: this API provides access to GKE.
To enable these APIs, visitenable the APIs in Google Cloud console.
Connect Cloud Shell to your Google Cloud project
Now that you've set up your Google Cloud project, you need to launch aCloud Shell instance and connect it to your Google Cloudproject. Cloud Shell is a command-line tool that lets you create and manage aproject's resources directly from your browser. Cloud Shell comespreinstalled with two important tools: thegcloud CLIand thekubectl CLI. In thistutorial, you use the gcloud CLI to interact with Google Cloud and inthe next tutorial, you use thekubectl CLI to manage the Cymbal Books appthat runs on GKE.
To connect a Cloud Shell instance with your Google Cloud project, followthese steps:
Go to the Google Cloud console:
In the console, click theActivate Cloud Shell button:

A Cloud Shell session opens inside a frame lower on the console.
Set your default project in the Google Cloud CLI using the following command:
gcloudconfigsetprojectPROJECT_IDReplace
PROJECT_IDwith the project ID of theproject that you created or selected in the previous section,Select or create Google Cloud project.A project ID is a unique string that differentiates your project from allother projects in Google Cloud. To locate the project ID, go to theproject selector.On that page, you can see the project IDs for each of your Google Cloudprojects.
Set the default environment variables
To simplify the commands that you run throughout this tutorial, you now set someenvironment variables in Cloud Shell. These variables store values such asyour project ID, repository region, and image tag. After you define thesevariables, you can reuse them in multiple commands by referencing the variablename (for example,$REPOSITORY_NAME) instead of retyping or replacing valueseach time. This approach makes the tutorial easier to follow and reduces therisk of errors.
To set up your environment with Cloud Shell, perform the following steps:
exportPROJECT_ID=$(gcloudconfiggetproject)exportREPOSITORY_REGION=REPOSITORY_REGIONexportREPOSITORY_NAME=REPOSITORY_NAMEexportREPOSITORY_DESCRIPTION="REPOSITORY_DESCRIPTION"exportTAG=TAGReplace the following:
REPOSITORY_REGION: theregion where you want yourArtifact Registry repository to be hosted. For example,us-central1(Iowa),us-west1(Oregon), oreurope-west1(Belgium). For a completelist of regions, seeRegions and Zones.REPOSITORY_NAME: the name of your repository.For example,book-review-service-repo.REPOSITORY_DESCRIPTION: a brief description ofthe repository's purpose. For example,"Repository for storing Dockerimages for the book review service".TAG: the tag that you want to apply to an image.A tag is a label that you can attach to a specific version of a containerimage. You can use tag naming conventions like these to clearly indicatedifferent versions of an image:v1v1.2.3- A descriptive tag, such as
feature-x-dev - A tag that indicates the environment, such as
test
Create a repository in Artifact Registry
Next, you create a repository in Artifact Registry. A repository is astorage location where you keep container images. When you build a containerimage, you need somewhere to store it so that it can later be deployed to aKubernetes cluster. Artifact Registry lets you create and manage these repositorieswithin your Google Cloud project.
To create a repository in Artifact Registry, run the following command:
gcloudartifactsrepositoriescreate${REPOSITORY_NAME}\--repository-format=docker\--location=${REPOSITORY_REGION}\--description="${REPOSITORY_DESCRIPTION}"Successful output from the command looks like the following:
Waiting for operation [...] to complete...done.Created repository [book-review-service-repo].Configure Docker for Artifact Registry
Next, you configure Docker so that it can securely communicate withGoogle Cloud's Artifact Registry. Docker is a tool that you can use topackage and run software in a consistent way across different environments. Youlearn more about how Docker works in the next section. For now, you need toconfigure it so that it can connect to Artifact Registry.
If you don't configure Docker in this way, you can't push the container imagesto Artifact Registry (a task you perform later in this tutorial). You also can't pullthe container images from Artifact Registry and deploy them to a GKEcluster (a task you perform in the next tutorial).
To configure Docker to authenticate with Artifact Registry, run this command:
gcloudauthconfigure-docker${REPOSITORY_REGION}-docker.pkg.devGet the tutorial code
Now that your Cloud Shell environment is configured, you need to download thetutorial code within Cloud Shell. Even if you previously cloned therepository on your local machine, you need to clone it again here on yourCloud Shell instance.
Although it's possible to complete this tutorial on your local machine, you wouldhave to manually install and configure several tools such as Docker,kubectl,and gcloud CLI. Using Cloud Shell is easier because it comespreconfigured with all of these tools.
In your Cloud Shell instance, run the following command to clone the GitHubrepository:
gitclonehttps://github.com/GoogleCloudPlatform/kubernetes-engine-samples.gitContainerization basics: container images, containers, and Dockerfiles
Now that you have set up your environment and downloaded the containerized code,you're ready to containerize the app. Containerizing the app consists of using aDockerfile to package each module of Cymbal Books (homepage, book details,images, and book reviews) into a container image. When the application isdeployed to the GKE cluster, Kubernetes uses these containerimages to create running containers in the cluster.
The following sections explain these concepts in detail.
What is containerization?
Containerization packages a module and all its dependencies, such as librariesand configuration files, into a unit called a container image. Developers usethis container image to create and run containers across anyenvironment—from a developer's laptop to a testing server or a productionKubernetes cluster.
What are container images?
A container image contains all the files that are needed to run an application.These files include the application code itself, system libraries, the runtimeenvironment (for example, the Python interpreter), static data, and any otherdependencies.
In this tutorial, you create a container image for each module of the bookreviews app.
What is a container?
A container is an isolated environment where code from a container image runs.You can create containers in two ways: by using thedocker run command fortesting during development, or by deploying container images to a Kubernetescluster.
In the containerized version of the Cymbal Books app, each module from themodular app runs in its own container:
- The homepage container runs the homepage module and handles requests to
/. - The book details container runs the book details module and serves data forendpoints such as
/book/1or/book/3. - The book reviews container runs the book reviews module and manages requeststo endpoints such as
/book/2/reviews. - The images container runs the images module and serves book cover images forendpoints such as
/images/fungi_frontier.jpg.
A key advantage of containers is that Kubernetes can automatically create morecontainers when needed. For example, if numerous users are reading book reviews,Kubernetes can start additional book reviews containers to handle the load.
To implement scaling in a modular app that doesn't use containers, you'd need towrite custom code to launch new instances of a module and distribute trafficbetween them. With Kubernetes, this scaling capability is built-in: you don'tneed to write any custom scaling code.
What are Dockerfiles?
A Dockerfile is a script that defines how to package a module into a containerimage. In this tutorial, you don't need to create any Dockerfiles—they'realready provided for you in the GitHub repository you cloned earlier. Eachmodule's directory in your local copy ofkubernetes-engine-samples/quickstarts/monolith-to-microservices/containerized/contains its own Dockerfile.
For example, you can find thehome_app module's Dockerfile in yourCloud Shell instance atkubernetes-engine-samples/quickstarts/monolith-to-microservices/containerized/home_app/Dockerfile.This Dockerfile looks like the following:
# Dockerfile for home_appFROM python:3.9-slim #line 1WORKDIR /app #line 2COPY requirements.txt . #line 3RUN pip install --no-cache-dir -r requirements.txt #line 4COPY home_app.py . #line 5COPY templates/ ./templates/ #line 6COPY static/ ./static/ #line 7CMD ["python", "home_app.py"] #line 8This Dockerfile performs the following steps to create the container image forthehome_app module:
- Line 1:
FROM python:3.9-slimdownloads a Python 3.9 interpreter andits required files into the container image. These files enable the moduleto run. - Line 2:
WORKDIR /appcreates a directory called/appinside thecontainer and sets this directory as the current working directory. Allcommands that are run inside the container will run from within thisdirectory. - Lines 3 and 4:
COPY requirements.txt .copies therequirements.txtfile from your local machine into the container image's/appdirectory.Therequirements.txtfile lists all the Python libraries thathome_app.pyneeds. The lineRUN pip installinstalls those librariesinto the container image. - Lines 5 to 7: The
COPYcommands that appear in these lines copy themodule's code (home_app.py) and its supporting files (templates and staticassets) to the/appdirectory within the container image. - Line 8:
CMDspecifies the default command that Docker runs when thecontainer starts. In this Dockerfile,CMD ["python", "home_app.py"]tellsDocker to use the Python interpreter to execute thehome_app.pymoduleautomatically when the container is launched.
How containerization can enforce stricter data isolation
Lines 5 to 7 of the Dockerfile, which were described in the previous section,show how containerization can enforce stricter data isolation than themodularized version of the app. In a previous tutorial, in the sectionGiveeach module access to only the data it needs, youlearned that the modular version of the app organized data into separatedirectories, but modules still shared the same file system and could potentiallyaccess each other's data.
Here, in the containerized version of the app, each module's container includesonly its necessary files. For example, if thehome_app module doesn't needaccess to book reviews data, that data simply doesn't exist inside thehome_app container. By default, a container can't access files from anothercontainer unless explicitly configured to do so. This helps ensure that eachmodule is fully isolated, and also helps prevent accidental or unauthorized dataaccess.
In the next section, you see how thedocker build command takes a Dockerfileas input, and follows the instructions in the Dockerfile to create a containerimage.
Build container images using Docker
In this section, you build Docker container images for each of the book reviewmodules and push them to your Artifact Registry repository. You'll use these containerimages in a following tutorial to deploy and run the Cymbal Books sample app inKubernetes.
Navigate to the root directory of the containerized application:
cdkubernetes-engine-samples/quickstarts/monolith-to-microservices/containerized/Create the container images by using the
docker buildcommand:dockerbuild-t${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/home-app:${TAG}./home_appdockerbuild-t${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-details-app:${TAG}./book_details_appdockerbuild-t${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-reviews-app:${TAG}./book_reviews_appdockerbuild-t${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/images-app:${TAG}./images_appView the container images that were built inside your Cloud Shellinstance:
dockerimagesCheck that the following images appear in the list:
home-appbook-details-appbook-reviews-appimages-app
If all four images are listed, you successfully created the containerimages.
Test containers in Cloud Shell
To verify that the container images are built correctly, you can run them ascontainers and test their endpoints in Cloud Shell.
Thebook_details_app,book_reviews_app, andimages_app containers can betested individually because they don't need to communicate with each other.However, testing thehome_app container by using Docker is difficult becausehome_appis configured to find the other containers that use service namessuch ashttp://book-details-service:8081.
Although it's possible to test thehome_app container by finding eachcontainer's IP address and configuringhome_app to use them instead of servicenames, this approach requires a lot of effort. Instead, it's a good idea todefer testing thehome_app container until after you deploy the application toa Kubernetes cluster. After the app is on the cluster, you can determine whetherthe home module is working correctly.
Follow these steps to test the containers:
Start the
book_details_app,book_reviews_app, andimages_appcontainers:dockerrun-d-p8081:8080${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-details-app:${TAG}dockerrun-d-p8082:8080${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-reviews-app:${TAG}dockerrun-d-p8083:8080${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/images-app:${TAG}Check that the containers are running by listing all active containers:
dockerpsOutput from this command should show three containers that are running,with the status
Up:CONTAINER ID IMAGE PORTS STATUSa1b2c3d4e5f6 REGION/.../details 0.0.0.0:8081->8080/tcp Upg7h8i9j0k1l2 REGION/.../reviews 0.0.0.0:8082->8080/tcp Upm3n4o5p6q7r8 REGION/.../images 0.0.0.0:8083->8080/tcp UpTo test the endpoints of the
book_details_appcontainer, use the followingcurlcommands:curlhttp://localhost:8081/bookscurlhttp://localhost:8081/book/1curlhttp://localhost:8081/book/2curlhttp://localhost:8081/book/3Each of these commands returns data in JSON format. For example, the outputfrom the
curl http://localhost:8081/book/1command looks like this:{"author":"Aria Clockwork","description":"In a world where time is a tangible substance, a young clockmaker discovers she can manipulate the fabric of time itself, leading to unforeseen consequences in her steampunk-inspired city.","id":1,"image_url":"zephyrs_timepiece.jpg","title":"Zephyr's Timepiece","year":2023}Retrieve book reviews from the
book_reviews_appcontainer by using thiscurlcommand:curlhttp://localhost:8082/book/1/reviewsThis command returns a list of 20 reviews of book 1 in JSON format. Here'san example of one review from the list:
{"content": "The concept of time as a tangible substance is brilliantly explored in 'Zephyr's Timepiece'.","rating": 5}Test the
images_appcontainer:Click the
**Web Preview**buttonSelectChange port and enter 8083. A browser window opens with a URLsimilar to this:
https://8083-your-instance-id.cs-your-region.cloudshell.dev/?authuser=0Remove
?authuser=0at the end of the URL and add the path to an imagefile, such as/images/fungi_frontier.jpg. The following is an example:https://8083-your-instance-id.cs-your-region.cloudshell.dev/images/fungi_frontier.jpgYou should see the book cover image forFungi Frontier displayed inyour browser.
After testing, stop the containers to release resources:
List the running containers and find their container IDs:
dockerpsStop each container:
dockerstopCONTAINER_IDReplace
CONTAINER_IDwith the ID of the container youwant to stop.
Push the container images to Artifact Registry
Before you can deploy your app to a Kubernetes cluster, the container imagesneed to be stored in a location that the cluster can access. In this step, youpush the images to the Artifact Registry repository you created earlier. In the nexttutorial, you deploy those images from the Artifact Registry repository to aGKE cluster:
To push your container images to Artifact Registry, run these commands:
dockerpush${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/home-app:${TAG}dockerpush${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-details-app:${TAG}dockerpush${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-reviews-app:${TAG}dockerpush${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/images-app:${TAG}After pushing the images, verify that they were successfully uploaded bylisting them:
gcloudartifactsdockerimageslist${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}You should see output similar to the following:
Listing items under project ${PROJECT_ID}, location ${REPOSITORY_REGION}, repository ${REPOSITORY_NAME}.IMAGE: ${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-details-appDIGEST: sha256:f7b78f44d70f2eedf7f7d4dc72c36070e7c0dd05daa5f473e1ebcfd1d44b95b1CREATE_TIME: 2024-11-14T00:38:53UPDATE_TIME: 2024-11-14T00:38:53SIZE: 52260143IMAGE: ${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/book-reviews-appDIGEST: sha256:875ac8d94ef54db2ff637e49ad2d1c50291087623718b854a34ad657748fac86CREATE_TIME: 2024-11-14T00:39:04UPDATE_TIME: 2024-11-14T00:39:04SIZE: 52262041IMAGE: ${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/home-appDIGEST: sha256:70ddc54ffd683e2525d87ee0451804d273868c7143d0c2a75ce423502c10638aCREATE_TIME: 2024-11-14T00:33:56UPDATE_TIME: 2024-11-14T00:33:56SIZE: 52262412IMAGE: ${REPOSITORY_REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY_NAME}/images-appDIGEST: sha256:790f0d8c2f83b09dc3b431c4c04d7dc68254fecc76c48f00a83babc2a5dc0484CREATE_TIME: 2024-11-14T00:39:15UPDATE_TIME: 2024-11-14T00:39:15SIZE: 53020815The output includes the following details for each image:
- IMAGE: the repository path and image name.
- DIGEST: a unique identifier for the image.
- CREATE_TIME or UPDATE_TIME: when the image was created or lastmodified.
- SIZE: the size of the image in bytes.
Update the Kubernetes manifest with paths to container images
As you learned in the previous tutorial,Prepare the modular app for containerization, aKubernetes manifest is a YAML file that defines how your app runs in aKubernetes cluster. It includes details such as the following:
- The modules of your app (for example,
home-app,book-details-app) - Paths to the container images
- Configuration details such as resource limits
- Service definitions for routing requests between modules
In this section, you update the same manifest file that you reviewed in theprevious tutorial. That file iskubernetes-manifest.yaml and it containsplaceholder values for the image paths. You need to replace those placeholderswith the actual paths to the container images you pushed to your Artifact Registryrepository in the previous section.
To update thekubernetes-manifest.yaml Kubernetes manifest file, follow thesesteps:
In Cloud Shell, navigate to the
containerized/directory, whichcontains the Kubernetes manifest filekubernetes-manifest.yaml:cdkubernetes-engine-samples/quickstarts/monolith-to-microservices/containerized/Open the
kubernetes-manifest.yamlfile in a text editor:vimkubernetes-manifest.yamlLocate the
imagefields that contain placeholders such as this:image: REPOSITORY_REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY_NAME/home-app:TAGReplace each placeholder with the actual paths to the container images thatyou pushed to Artifact Registry:
REPOSITORY_REGION: the region that you specified when youcreated a repository in Artifact Registry.PROJECT_ID: your Google Cloud project ID, which youcan find in theproject selectorpage.REPOSITORY_NAME: the name of your repository, whichyou specified when youcreated a repository in Artifact Registry.For example,book-review-service-repo.TAG: the tag that you chose when youBuilt the container images.
Here's what a path might look like after making these replacements:
image:us-west1-docker.pkg.dev/your-project-id/book-review-service-repo/home-app:v1Update the paths for all container images:
home-appbook-details-appbook-reviews-appimages-app
After you update the paths, save the manifest file and close the editor. Forexample, if you're using vim, pressEsc to enter command mode,typewq, and pressEnter to save and exit.
Your Kubernetes manifest is now configured to deploy the container images fromyour Artifact Registry repository to a Kubernetes cluster.
Summary
In this tutorial, you prepared the modular Cymbal Books app for deployment to aKubernetes cluster by performing the following tasks:
- Set up a Google Cloud project and configured Cloud Shell for yourenvironment.
- Reviewed the provided Dockerfiles for each app module.
- Built container images for the app modules by using Docker.
- Tested containers in Cloud Shell to verify their functionality.
- Pushed the container images to Artifact Registry for storage.
- Updated the Kubernetes manifest to use the correct container image pathsfrom Artifact Registry.
What's next
In the next tutorial,Deploy the app to a GKE cluster,you deploy the containerized application to a GKE cluster.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-12-15 UTC.