CKAD - Define, build and modify container images
![CKAD - Define, build and modify container images](/content/images/size/w960/2024/03/CKAD.jpeg)
How to build and alter images for basic purposes and to a level needed for the CKAD Exam.
Requirements
Podman installed on local machine.
Basic Image and Container knowledge
Building a new Image
The first thing needed is a file specifying an image to build an image. In Docker terminology, that is usually called the DOCKERFILE
or Dockerfile
; Podman will call this the more generic Containerfile
; the file can be called anything and specified with a flag on the image build. If the flag is not used, the file name must match the default expected for the build system and be in a location relative to where the command is run.
Simple Image File
All images start with a base object; sometimes, that beginning point is called 'scratch', and other times, another image, such as Centos or Ubuntu. For most purposes, an image file will start with an existing base Image, not scratch.
Example 1:
FROM scratch
Scratch Example
Example 2:
FROM alpine:3.19.1
OS Example
From this starting point, more changes can be added to the Containerfile.
Building a small server in go
We will build and change a small server written in Golang for the remaining examples. Neither scratch nor alpine will be sufficiently easy; we will use the image golang:1.22.0
, which includes the building code we need.
We need two files: our container file, which we will continue to build, and the small snippet of go code to compile in our Image.
Create an empty directory on your machine where we can do work.
We will do all the work in the folder specified below in this article. /Users/codadensys/ckad/build/
Copy and paste the contents below into a file named server.go
. Hat tip to hendrikmaus
package main
import (
"fmt"
"flag"
"log"
"net/http"
)
func main() {
listen := flag.String("listen", ":8080", "Provide address:port to listen on")
flag.Parse();
log.Printf("Starting server on %s", *listen)
http.HandleFunc("/", httpHandleRoot)
http.HandleFunc("/ping", httpHandlePing)
log.Fatal(http.ListenAndServe(*listen, nil))
}
func httpHandleRoot(res http.ResponseWriter, req *http.Request) {
http.Redirect(res, req, "/ping", http.StatusSeeOther)
}
func httpHandlePing(res http.ResponseWriter, req *http.Request) {
res.Header().Add("X-Powered-By", "golang")
res.WriteHeader(http.StatusOK)
fmt.Fprint(res, "pong")
}
server.go
Let's also create a file for the new Image, and we will call it pingpongimage
Create pingpongimage
file and add the below content.
FROM golang:1.22.0
COPY server.go .
pingpongimage
This image file will build on golang:1.22.0
image and copy our file for the server into it.
The local file system should look like the below in the build
folder.
build/
├── pingpongimage
└── server.go
Ensure you are in the build
directory and run the command below.
podman image build -f pingpongimage -t pingpongserver:0.0.1
podman build
The output from an image build should look like the below
# OUTPUT
STEP 1/2: FROM golang:1.22.0
--> 23a095adf224
STEP 2/2: COPY server.go .
--> 24a4554dge45
COMMIT pingpongserver:0.0.1
--> 0dad1905a224
Successfully tagged localhost/pingpongserver:0.0.1
0dad1905a224c250f596cb82f70d3ebad888a1767523f7d0f9202072e9247b20
Each line in the Containerfile is a step and "layer" in the image build process.
Now that it is built verify it is stored locally.
podman image list
List the images on local machine with podman
There should now be at least two images on the local machine: our newly built pingpongserver
image and the image it built on library/golang
image.
#OUTPUT
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/pingpongserver 0.0.1 0dad1905a224 About a minute ago 854 MB
docker.io/library/golang 1.22.0 9cbeef2f2690 3 weeks ago 854 MB
Modify an image with more building
Change the Containerfile
Now currently, the built Image doesn't do anything, and it didn't do anything on the we can test other than copy the file. So let's modify it and have the image build do something useful.
Add a line to build the go code and run it on container start-up.
In the pingpongimage
file, add a few lines.
FROM golang:1.22.0
COPY server.go .
RUN CGO_ENABLED=0 go build server.go
CMD ["/server"]
Don't worry about the RUN line, this is just golangs way to compile the go code and make an executable file called server
. The CMD however is important as this tells the container what to execute on startup.
Update the semantic version of the newly built image and rerun the build process.
podman image build -f pingpongimage -t pingpongserver:0.0.2
## OUTPUT
STEP 1/4: FROM golang:1.22.0
STEP 2/4: COPY server.go .
--> Using cache 0dad1905a224c250f596cb82f70d3ebad888a1767523f7d0f9202072e9247b20
--> 0dad1905a224
STEP 3/4: RUN CGO_ENABLED=0 go build server.go
--> 8ad61ffe0874
STEP 4/4: CMD ["./server"]
COMMIT pingpongimage:0.0.2
--> e9709d1a4119
Successfully tagged localhost/pingpongimage:0.0.2
e9709d1a4119ce976d2333834bbfe7b3b4022b9b9bcdae6636ac6e98d1718cf4
Now a new image is built and it actually does something to test.
With a new image built, we can run the command below.
podman run -d -p 9999:8080 localhost/pingpongimage:0.0.2
This command will run the container, and on the local machine expose port 9999 and forward it to port 8080 in the container. If a current process is running that uses port 9999 this will fail.
When successful the output will look like below.
podman run -d -p 9999:8080 localhost/pingpongimage:0.0.2
# OUTPUT
c2b75362a4678eaf683efccab0c78607ac6bf4f9892d0fad86de204a0c9e5cba
We now have the image on the local machine and the running container.
With the command above executed, we can run a small test to see what the running container does.
It's a ping-pong server, so let's send it a ping.
curl localhost:9999/ping
send the ping
Ping sent, pong received.
curl localhost:9999/ping
# OUTPUT
pong
receive the pong
We have successfully taken our new image and built it up to do something, not that both are valid images, though our first one would not run and do anything.
Create a new Image from an Image.
This method is a different way, but not really.
Take our new image and make a new Contianrefile with it as the base.
FROM localhost/pingpongimage:0.0.2
RUN rm /usr/bin/bash #Secretly advanced CKS knowledge
Build this new Contianerfile with podman commands again.
podman image build -f newpingpongimage -t newpingpongserver:0.0.1
This command will use the exisitng image pingpongimage:0.0.2
and add onemore layer.
podman image build -f newpingpongimage -t newpingpongserver:0.0.1
# OUTPUT
STEP 1/2: FROM localhost/pingpongimage:0.0.2
STEP 2/2: RUN rm /usr/bin/bash #Secretly advanced CKS knowledge
COMMIT newpingpongserver:0.0.1
--> ac02ed877435
Successfully tagged localhost/newpingpongserver:0.0.1
ac02ed8774353fee1e04bc877121afb59c47408761c5a00a1ba31af15b36cc65
This would be the equivalent of a single Containerfile
FROM golang:1.22.0
COPY server.go .
RUN CGO_ENABLED=0 go build server.go
CMD ["/server"]
RUN rm /usr/bin/bash #Secretly advanced CKS knowledge
notional equivalent Containerfile
Since we changed something, we removed the bash shell from the image, lets make sure the container still works. Since last container is still running on port 9999 it will need to be removed first.
podman run -d -p 9999:8080 localhost/newpingpongserver:0.0.1
# OUTPUT
ee84429a87cb8e35bc6f961675c0d4ed8b9e90abc7ddd39b6dcd01fb15544c61
# New curl try
curl localhost:9999/ping
# OUTPUT
pong
rerun and send the ping
What else can we do to create and modify Images?
Create a new Image from a running Container.
We can create and alter an existing image, but sometimes, we may need to make a new image from a running container. Ideally, we would recreate a Container file, but occasionally, we must take an altered running container and create an image.
Let's alter the last running image of the newpingpongserver
Exec into it and inspect the file system
podman exec -it sharp_jemison /bin/bash
Uh oh error!
Error: crun: executable file `/bin/bash` not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found
Thankfully we only deleted bash and not shell.
podman exec -it sharp_jemison /bin/sh
Now that you are successfully in the container look around. Run ls
command.
ls
# OUTPUT
bin server server.go src
list files inside newpingpongserver
The file server
is running the ping pong server; now, we no longer need the server.go
file.
Remove it to test a small change.
rm server.go
Now exit the container.
Export and the running container into an saved tar file of the image layers, and then reimport it into the local file system images.
Here, my container ID is ee84429a87cb
.
podman export ee84429a87cb > container.tar
podman import container.tar localhost/newpingpongserver:0.0.2
NOTE: podman commit
should do this in one line.
Now check to see the new image listed with a new and different `IMAGE ID`
podman image list
# OUTPUT
# New Image
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/newpingpongserver 0.0.2 d26282904ba1 13 seconds ago 922 MB
#Previous Images
localhost/newpingpongserver 0.0.1 ac02ed877435 3 hours ago 928 MB
localhost/pingpongimage 0.0.2 e9709d1a4119 23 hours ago 928 MB
localhost/pingpongimage 0.0.1 0dad1905a224 23 hours ago 854 MB
Feel free to start the new image up again into a container and test again.
Conclusion
You should successfully understand how to build an image using a few techniques. Using Containerfile is the best approach as this file is now trackable in Git for proper GitOps and change management.
The pingpongserver image will be used again later.