1. Overview
Docker is an open platform for developing, shipping, and running applications. With Docker, you can separate your applications from your infrastructure and treat your infrastructure like a managed application. Docker helps you ship code faster, test faster, deploy faster, and shorten the cycle between writing code and running code.
Docker does this by combining kernel containerization features with workflows and tooling that helps you manage and deploy your applications.
Docker containers can be directly used in Kubernetes, which allows them to be run in the Kubernetes Engine with ease. After learning the essentials of Docker, you will have the skillset to start developing Kubernetes and containerized applications.
What you'll learn
In this lab, you will learn how to do the following:
- Create a Dockerfile for a sample application
- Build an image
- Run the image as a container locally
- Change container behavior
- Push the image to Artifact Registry
Prerequisites
This is an introductory level lab. Little to no prior experience with Docker and containers is assumed. Familiarity with Cloud Shell and the command line is suggested, but not required.
Self-paced environment setup
- Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.
- The Project name is the display name for this project's participants. It is a character string not used by Google APIs, and you can update it at any time.
- The Project ID must be unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference the Project ID (and it is typically identified as
PROJECT_ID
), so if you don't like it, generate another random one, or, you can try your own and see if it's available. Then it's "frozen" after the project is created. - There is a third value, a Project Number which some APIs use. Learn more about all three of these values in the documentation.
- Next, you'll need to enable billing in the Cloud Console in order to use Cloud resources/APIs. Running through this codelab shouldn't cost much, if anything at all. To shut down resources so you don't incur billing beyond this tutorial, follow any "clean-up" instructions found at the end of the codelab. New users of Google Cloud are eligible for the $300 USD Free Trial program.
2. Sample Application
A sample application has been provided to facilitate this lab. In this section you will retrieve the source code and build the application in its native form before moving on to the containerization process.
Source Code
The source code for this lab is available in the GoogleCloudPlatform/container-developer-workshop repository along with the sample application documentation.
Configure git
git config --global user.name ${USER}
git config --global user.email ${USER}@qwiklabs.net
Clone the sample application Cloud Source Repository
gcloud source repos clone sample-app ${HOME}/sample-app &&
cd ${HOME}/sample-app &&
git checkout main
Output
Cloning into '/home/student_03_49720296e995/sample-app'... remote: Finding sources: 100% (16/16) remote: Total 16 (delta 0), reused 16 (delta 0) Receiving objects: 100% (16/16), 47.23 KiB | 681.00 KiB/s, done. warning: remote HEAD refers to nonexistent ref, unable to checkout. Project [qwiklabs-gcp-02-4327c4e03d82] repository [sample-app] was cloned to [/home/student_03_49720296e995/sample-app]. Branch 'main' set up to track remote branch 'main' from 'origin'. Switched to a new branch 'main'
Build the sample application
cd ${HOME}/sample-app
./mvnw compile
Output
[INFO] Scanning for projects... ... [INFO] Compiling 1 source file to /home/student_03_49720296e995/sample-app/target/classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 10.080 s [INFO] Finished at: 2022-02-23T17:14:30Z [INFO] ------------------------------------------------------------------------
Run the sample application
cd ${HOME}/sample-app
./mvnw exec:java
Output
[INFO] Scanning for projects... ... Listening at http://localhost:8080
Preview the running application
- Click the Cloud Shell Web Preview button
- Click Preview on port 8080
When you're done
- Press CTRL + c in Cloud Shell to stop the running application
3. Dockerfile
Containerizing the Application with a Dockerfile
One method of packaging an application into a container is with the use of a Dockerfile. The Dockerfile is similar to a script which instructs the daemon on how to assemble the container image. See the Dockerfile reference documentation) for more information.
Create an empty Dockerfile in the sample application repository.
touch ${HOME}/sample-app/Dockerfile
Open the Dockerfile in your editor of choice.
vi ${HOME}/sample-app/Dockerfile
Choose a starting image
Using the Dockerfile method to build a container requires direct knowledge about the application in order to assemble the container. The first step to creating a Dockerfile is selecting an image that will be used as the basis of your image.This image should be a parent or base image maintained and published by a trusted source, usually your company.
The FROM
instruction initializes a new build stage and sets the base image for subsequent sequential commands. Thus the FROM
instruction is usually the first instruction in a Dockerfile and can only be preceded by an optional ARG instruction to support variables.
Syntax: FROM <image>[:<tag> | @<digest>] [AS <name>]
The format for an image is <image>:<tag>
or <image>@<digest>
. If a tag or digest is not specified it defaults to the :latest
tag. The format of <image>
varies based on the registry used to store the image. For Artifact Registry the <image>
format is <region>-docker.pkg.dev/<project ID>/<repository name>/<image name>:<image tag>
.
For this lab we use the public openjdk:11.0-jdk
image, add the following line to your Dockerfile
FROM openjdk:11.0-jdk
Set the working directory
The WORKDIR
instruction sets the working directory for any sequential instructions that follow in the Dockerfile. For more information see the WORKDIR section of the Dockerfile reference documentation
Syntax: WORKDIR <path>
For this lab we use the /app
directory as our WORKDIR
, add the following line to the bottom of your Dockerfile
WORKDIR /app
Copy the application files
The COPY
instruction copies directories or files from the <source>
location to the <destination>
path of the image filesystem. Multiple <source>
resources can be specified and they are all relative to the build context. The build context will be discussed further in the Build section. For more information see the COPY section of the Dockerfile reference documentation
Syntax: COPY <source>... <destination>
For this lab we will copy all the files in the repository to the image filesystem, add the following line to the bottom of your Dockerfile
COPY . /app
Compile the application
The RUN
instruction executes commands in a new image layer on top of the current image and commits the results. The resulting committed image will be used for sequential steps in the Dockerfile. For more information see the RUN section of the Dockerfile reference documentation
Syntax: RUN <command>
For this lab we will use Maven to compile the application to a JAR file, add the following line to the bottom of your Dockerfile
RUN ./mvnw compile assembly:single
Start the application
The CMD
instruction provides the default command for a running container. There can only be one CMD instruction in a Dockerfile, if more than one CMD is specified then only the last CMD will take effect. There is more advanced functionality available using both the CMD and ENTRYPOINT instructions, but that is not covered in this lab. For more information see the CMD` section of the Dockerfile reference documentation
Syntax: CMD ["executable","param1","param2"]
For this lab we run the JAR file we compiled, add the following line to the bottom of your Dockerfile
CMD ["java","-jar","/app/target/sample-app-1.0.0-jar-with-dependencies.jar"]
Final Dockerfile
The final Dockerfile will be
FROM openjdk:11.0-jdk
WORKDIR /app
COPY . /app
RUN ./mvnw compile assembly:single
CMD ["java","-jar","/app/target/sample-app-1.0.0-jar-with-dependencies.jar"]
Commit the Dockerfile locally
cd ${HOME}/sample-app
git add Dockerfile
git commit -m "Added Dockerfile"
4. Build
Now we will build the image from the Dockerfile by using the docker build
command. This command instructs the docker daemon to build the image using the instructions from our Dockerfile. See the docker build reference documentation for more information.
Build the image
cd ${HOME}/sample-app
export IMAGE_TAG=$(git rev-parse --short HEAD)
docker build --tag sample-app:${IMAGE_TAG} .
Output
Sending build context to Docker daemon 221.2kB Step 1/4 : FROM openjdk:11.0-jdk 11.0-jdk: Pulling from library/openjdk 0c6b8ff8c37e: Pull complete 412caad352a3: Pull complete e6d3e61f7a50: Pull complete 461bb1d8c517: Pull complete e442ee9d8dd9: Pull complete 542c9fe4a7ba: Pull complete 41de18d1833d: Pull complete Digest: sha256:d72b1b9e94e07278649d91c635e34737ae8f181c191b771bde6816f9bb4bd08a Status: Downloaded newer image for openjdk:11.0-jdk ---> 2924126f1829 Step 2/4 : WORKDIR /app ---> Running in ea037abb273d Removing intermediate container ea037abb273d ---> bd9b6d078082 Step 3/4 : COPY . /app ---> b9aec2b5de51 Step 4/4 : RUN ./mvnw compile jar:jar ---> Running in 3f5ff737b7fd [INFO] Scanning for projects... ... [INFO] Building jar: /app/target/sample-app-1.0.0.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 22.952 s [INFO] Finished at: 2022-02-23T18:09:08Z [INFO] ------------------------------------------------------------------------ Removing intermediate container 331443caebd3 ---> 152f65cc441e Step 5/5 : CMD ["java", "-jar", "/app/target/sample-app-1.0.0.jar"] ---> Running in 3d595a72231c Removing intermediate container 3d595a72231c ---> 0e40d7548cab Successfully built 0e40d7548cab Successfully tagged sample-app:aaa8895
5. Run
Upon successful build of our container image, we are now able to run our application and make sure that it behaves as expected using the docker run command. This command will launch our container in the foreground of our command prompt for testing or debugging. See the docker run reference documentation for more information.
Run a container using the image
cd ${HOME}/sample-app
export IMAGE_TAG=$(git rev-parse --short HEAD)
docker run \
--rm \
-p 8080:8080 \
sample-app:${IMAGE_TAG}
Output
Listening at http://localhost:8080
Preview the application running in a container
- Click the Cloud Shell Web Preview button
- Click Preview on port 8080
- Press CTRL + c in Cloud Shell to stop the containers
Changing container behavior
Executing Docker Run uses the default configuration in the Dockerfile. Additional instructions and parameters can be added to modify this behavior.
Enable TRACE logging
cd ${HOME}/sample-app
export IMAGE_TAG=$(git rev-parse --short HEAD)
docker run \
--rm \
-p 8080:8080 \
sample-app:${IMAGE_TAG} \
java -Dorg.slf4j.simpleLogger.defaultLogLevel=trace -jar /app/target/sample-app-1.0.0-jar-with-dependencies.jar
Preview the application running in a container
- Click the Cloud Shell Web Preview button
- Click Preview on port 8080
- Switch to the Cloud Shell tab and see that additional logging
- Press CTRL + c in Cloud Shell to stop the container
Change port
cd ${HOME}/sample-app
export IMAGE_TAG=$(git rev-parse --short HEAD)
docker run \
--rm \
-e PORT=8081 \
-p 8081:8081 \
sample-app:${IMAGE_TAG}
Preview the application running in a container
- Click the Cloud Shell Web Preview button
- Click Change port
- Enter 8081
- Click Change and Preview
- Press CTRL + c in Cloud Shell to stop the container
6. Push
Once confident that the container image is running properly and we want to make this container available to run in other environments and/or by other users, we need to push the image to a shared repository. This should happen as part of an automated build pipeline but in our test environment we already have a repository configured and we can manually push our image.
Push the Dockerfile commit to the sample-app repository
cd ${HOME}/sample-app
export IMAGE_TAG=$(git rev-parse --short HEAD)
git push
Tag the image for Artifact Registry
docker tag sample-app:${IMAGE_TAG} \
us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/apps/sample-app:${IMAGE_TAG}
Configure your credentials for Artifact Registry
gcloud auth configure-docker us-central1-docker.pkg.dev
When prompted Do you want to continue (Y/n)?
answer y
and press Enter
Push the image to Artifact Registry
docker push us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/apps/sample-app:${IMAGE_TAG}
Output
The push refers to repository [us-central1-docker.pkg.dev/qwiklabs-gcp-04-b47ced695a3c/apps/sample-app] 453b97f86449: Pushed e86791aa0382: Pushed d404c7ee0850: Pushed fe4f44af763d: Pushed 7c072cee6a29: Pushed 1e5fdc3d671c: Pushed 613ab28cf833: Pushed bed676ceab7a: Pushed 6398d5cccd2c: Pushed 0b0f2f2f5279: Pushed aaa8895: digest: sha256:459de00f86f159cc63f98687f7c9563fd65a2eb9bcc71c23dda3351baf13607a size: 2424
7. Congratulations!
Congratulations, you finished the codelab!
What you've covered
- Created a Dockerfile for a sample application
- Built an image
- Ran the image as a container locally
- Changed container behavior
- Pushed the image to Artifact Registry