Images
Exercise - Build your image using commit (Optional)
To recap the essentials from the Container Exercises run a container in detached mode, using port mapping, environment variables and a custom name (base_container):
docker run -d -p 8080:8080 -e PROPERTY=Stuttgart --name base_container novatec/technologyconsulting-hello-container:v0.1
Milestone: DOCKER/IMAGES/BUILD-COMMIT
and validate if the container is doing what it is supposed to do:
curl localhost:8080/hello; echo
Hello World (from 34f40588f2e0) to StuttgartContainers are basically isolated processes. To find out more about this execute the ps command in the following way:
docker ps --no-trunc
Taking a closer look at the command tab you can see:
[...] COMMAND [...]
[...] "./application -Dquarkus.http.host=0.0.0.0" [...]This is the core process of your container.
Another way to find out more about your running container is to invoke:
docker container inspect base_container
Most likely this command will return more information than you can handle, but if you scroll through you may find this section:
"Config": {
"...": "..."
"Env": [
"PROPERTY=Stuttgart",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"container=oci"
],
"Cmd": [
"./application",
"-Dquarkus.http.host=0.0.0.0"
]
"...": "..."
}This one shows the environment properties set and the application process.
Or, for quick access, filtering the JSON output via the command-line JSON processor jq, listing just the environment settings:
docker container inspect base_container | jq '.[].Config.Env'
There also is a possibility to execute another process in the same scope by executing:
docker exec -it base_container /bin/bash
This will open a new shell process (/bin/bash) within the container and connect to it in interactive mode.
docker exec -it base_container /bin/bash
bash-4.4$This kind of feels like being “logged into” the container. Execute a couple of things, e.g. check which user (ID, that is) you are and list your file system information:
whoami
bash-4.4$ whoami
whoami: cannot find name for user ID 1001ls /
bash-4.4$ ls /
bin boot dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var workDisplay the environment variable
echo $PROPERTY
bash-4.4$ echo $PROPERTY
Stuttgartand create a file within the container
echo "this container has been modified" >> /work/info
and eventually exit the shell and return to your host OS via
exit
To copy a file from “outside” of the container, i.e. from the host OS, into the container filesystem, execute 2 commands
echo "mysterious file from outside" >> message
docker cp message base_container:/work
Now create a new container image from the running container using commit:
docker commit base_container hello-container:v0.2
If it is being created successfully the response should look like this:
$ docker commit base_container hello-container:v0.2
sha256:34a4911c09c2d9ea4982c59eafec867370fb0ddac4c14e7aa8ce3854a7b8c685Observe the changes in your local repository:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-container v0.2 34a4911c09c2 23 seconds ago 137MB
novatec/technologyconsulting-hello-container v0.1 c4550e0a7743 19 hours ago 137MBThe whole sequence can be displayed as follows:
sequenceDiagram
participant U as User
participant D as Docker Daemon
participant C as Container
U->>D: "docker exec ..."
D->>C: opens shell
Note over C: execute commands
C-->>D: logout
D-->>U: release terminal
U->>D: "docker cp ..."
D->>C: put file
Note over C: new file present
D-->>U: release terminal
U->>D: "docker commit ..."
Note over D: store new image
D-->>U: release terminal
Now try to do the following steps by yourself:
- Run a new container instance from the newly created image called new_container
- Observe the running container
- Run a shell in the new container
- Observe if the changes made to image have taken effect (meaning are the files still there under /work)
Recap: There are two images in your repo now. One was pulled from a remote repository, the second one was built locally based on a container based on the first image. The images in itself are immutable. State changes can only be applied to running containers. Unless this is persisted into a new image the changes are lost once a container terminates. Exit from your running container.
Stop and remove your recent containers again:
docker rm $(docker ps -q | xargs docker stop)
Milestone: DOCKER/IMAGES/BUILD-COMMIT-RM
Exercise - Build your images using Dockerfiles
Building containers via writing, copying files and running “docker commit” can be a cumbersome task and will always require manual steps, which can be error prone. A docker built-in mechanism to automate this is called Dockerfile.
In a Dockerfile you specify multiple steps to be done in sequence that result in a new container image.
There is a pre-built distributed application provided for you to be run in containers. Pull it from git and switch to the exercises directory:
cd && git clone https://github.com/NovatecConsulting/technologyconsulting-containerexerciseapp.git
cd /home/novatec/technologyconsulting-containerexerciseapp
Milestone: DOCKER/IMAGES/GIT-CLONE
You will find two Dockerfiles in this directory:
ls -ltr Dockerfile*
$ ls -ltr Dockerfile
-rw-rw-r-- 1 novatec novatec 121 Dec 1 10:19 Dockerfile-todoui
-rw-rw-r-- 1 novatec novatec 204 Dec 1 10:19 Dockerfile-todobackendHave a look at the backend one first:
cat --number Dockerfile-todobackend
$ cat --number Dockerfile-todobackend
1 FROM eclipse-temurin:17-alpine
2 RUN mkdir -p /opt/todobackend
3 WORKDIR /opt/todobackend
4 COPY todobackend/target/todobackend-0.0.1-SNAPSHOT.jar /opt/todobackend
5 CMD ["java", "-jar", "todobackend-0.0.1-SNAPSHOT.jar"]In order to execute this, run the docker command the following way:
docker build -f Dockerfile-todobackend -t todobackend:v0.1 .
This will tell docker to use the file with the given path (via -f) and tag a new image (-t) executed from the current directory (.)
Now a lot of incredible things happen:
[+] Building 23.0s (9/9) FINISHED docker:default
=> [internal] load build definition from Dockerfile-todobackend 2.3s
=> => transferring dockerfile: 255B 0.0s
=> [internal] load .dockerignore 2.6s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/eclipse-temurin:17-alpine 2.2s
=> [1/4] FROM docker.io/library/eclipse-temurin:17-alpine@sha256:60b102f57f99ee12190394019d4da827afd5bf6a182aa520f192993effbc4cf0 12.3s
=> => resolve docker.io/library/eclipse-temurin:17-alpine@sha256:60b102f57f99ee12190394019d4da827afd5bf6a182aa520f192993effbc4cf0 0.4s
=> => sha256:60b102f57f99ee12190394019d4da827afd5bf6a182aa520f192993effbc4cf0 1.64kB / 1.64kB 0.0s
=> => sha256:44b3cea369c947527e266275cee85c71a81f20fc5076f6ebb5a13f19015dce71 947B / 947B 0.0s
=> => sha256:a3562aa0b991a80cfe8172847c8be6dbf6e46340b759c2b782f8b8be45342717 3.40kB / 3.40kB 0.0s
=> => sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10 2.76MB / 2.76MB 0.3s
=> => sha256:f910a506b6cb1dbec766725d70356f695ae2bf2bea6224dbe8c7c6ad4f3664a2 238B / 238B 0.4s
=> => sha256:c2274a1a0e2786ee9101b08f76111f9ab8019e368dce1e325d3c284a0ca33397 70.73MB / 70.73MB 3.0s
=> => extracting sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10 0.6s
=> => extracting sha256:f910a506b6cb1dbec766725d70356f695ae2bf2bea6224dbe8c7c6ad4f3664a2 0.0s
=> => extracting sha256:c2274a1a0e2786ee9101b08f76111f9ab8019e368dce1e325d3c284a0ca33397 2.6s
=> [internal] load build context 1.5s
=> => transferring context: 34.45MB 1.1s
=> [2/4] RUN mkdir -p /opt/todobackend 1.5s
=> [3/4] WORKDIR /opt/todobackend 0.8s
=> [4/4] COPY todobackend/target/todobackend-0.0.1-SNAPSHOT.jar /opt/todobackend 1.2s
=> exporting to image 1.6s
=> => exporting layers 1.4s
=> => writing image sha256:0a6c8b63fb84d2f5ce8c9d97b200ba49b59581a8ed50051b95969684fa955d86 0.1s
=> => naming to docker.io/library/todobackend:v0.1 0.1s- In Step 1 a base image from the Docker Hub is being pulled (eclipse-temurin:17-alpine)
- Step 2 creates a new directory within the container
- Step 3 sets the default working directory in the container (to the one created in Step 2)
- A file from the local file system (todo…jar) is being copied into the container’s working dir in Step 4
- The last step (not explicitly show in the output) sets the process/command of the container. This one will be started if an instance is being run
- And finally a new image is being built and tagged with the name given in the command (todobackend:v0.1)
sequenceDiagram
participant U as User
participant D as Docker Daemon
participant C as Container
participant R as Remote Registry
U->>D: "docker build ..."
opt fetching base image
D->>R: image pull
R-->>D: image
end
D->>C: instantiate base container
loop execute
C->>C: runs in background until terminated
end
D->>C: execute further Steps
Note over C: Step X
Note over C: Step Y
Note over C: Step ...
C-->>D: release
Note over D: store new image
D->>C: terminate container
D-->>U: release terminal
Milestone: DOCKER/IMAGES/DOCKERFILE-TODOBACKEND, requires: DOCKER/IMAGES/GIT-CLONE
Have a look at your local images and validate if it is really there (as well as some others omitted here):
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
todobackend v0.1 fd13afea3880 5 minutes ago 369MBDo a test run, providing an environment variable that tells the backend to just use a simple in-memory database:
docker run -d -p 8080:8080 -e SPRING_PROFILES_ACTIVE=dev --name todobackend todobackend:v0.1
Milestone: DOCKER/IMAGES/TODOBACKEND-RUN, requires: DOCKER/IMAGES/GIT-CLONE
And have a look at the logs:
docker logs todobackend
Or even better keep watching the logs (-f means follow):
docker logs -f todobackend
Step out by pressing Ctrl+C
You will see some Spring Boot output:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.1)
...
2023-12-01 09:28:43.375 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
...
2023-12-01 09:28:54.593 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-12-01 09:28:54.606 INFO 1 --- [ main] i.n.todobackend.TodobackendApplication : Started TodobackendApplication in 20.842 seconds (JVM running for 22.476)If this output is displayed things went well.
Next step is to build the image for the UI part of the application.
Exercise - Write your own Dockerfile
Have a look at the 2nd Dockerfile in the directory:
cat Dockerfile-todoui
$ cat Dockerfile-todoui
FROM ------
RUN mkdir -p /opt/todoui
WORKDIR ------
COPY todoui/target/------ /opt/todoui
CMD ["java", "-jar", "------"]This one has some intended gaps in it and you need to replace the “-----” with the correct content. The structure of the UI component is equivalent to the backend component except the difference in the name.
Try yourself to:
- Complete the Dockerfile (using nano or vim)
- Build a new image with the Dockerfile
- Check the new image in the registry
- Run a container using a port mapping 8090:8090
Or look at the solution below if you have problems
Milestone: DOCKER/IMAGES/TODOUI-RUN, requires: DOCKER/IMAGES/GIT-CLONE
Exercise - Customize a standard image
So far there are two application containers running, which are based on images that are self-built via Dockerfiles. Often containers can also be useful through re-usability, when a certain functionality is already given and only needs to be customized slightly.
The sample application consists of 2 application components and a database. Many database vendors provide the databases as docker images. You can easily look them up at the docker Hub.
Open the following link in a new tab: Docker Hub Search
Here you can easily find base images for MongoDB, MySQL, Postgres and many more.
Open the link for Postgres also in a new tab: Docker Hub Postgres
If you scroll down on this page you will find instructions on how to run it, which parameters exists, examples and so on. This is pretty typical for most images you can find there.
For the application of this exercise a Postgres database is required with the following requirements:
- Name: postgresdb
- Port Mapping: 5432:5432
- Environment Variables:
- POSTGRES_USER=matthias
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
Try to construct the according docker run command yourself or have a look at the solution below.
Milestone: DOCKER/IMAGES/POSTGRES-RUN
You can check if the container has come up well using:
docker logs postgresdb
[...]
PostgreSQL init process complete; ready for start up.
2023-12-01 09:34:53.723 UTC [1] LOG: starting PostgreSQL 16.1 (Debian 16.1-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
2023-12-01 09:34:53.724 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2023-12-01 09:34:53.724 UTC [1] LOG: listening on IPv6 address "::", port 5432
2023-12-01 09:34:53.746 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2023-12-01 09:34:53.774 UTC [62] LOG: database system was shut down at 2023-12-01 09:34:53 UTC
2023-12-01 09:34:53.798 UTC [1] LOG: database system is ready to accept connectionsExercise - Validate and wrap-up (Optional)
List your currently running containers:
docker ps
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2a968f4f4b0 postgres:latest "docker-entrypoint.s…" 59 seconds ago Up 57 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp postgresdb
d266fc71ab06 todoui:v0.1 "/__cacert_entrypoin…" 3 minutes ago Up 3 minutes 0.0.0.0:8090->8090/tcp, :::8090->8090/tcp todoui
265da979af36 todobackend:v0.1 "/__cacert_entrypoin…" 7 minutes ago Up 7 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp todobackendList the images in your repo:
docker images
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
todoui v0.1 b2c2827e6804 4 minutes ago 338MB
todobackend v0.1 fd13afea3880 14 minutes ago 369MB
hello-container v0.2 34a4911c09c2 20 minutes ago 137MB
postgres latest 2167863c43fd 3 weeks ago 425MB
novatec/technologyconsulting-hello-container v0.1 c4550e0a7743 19 hours ago 137MBYou have now seen various ways to build container images, how to re-use existing ones and how to run and configure them according to your need. The problem with the current state is that the images are not able to talk to each other. Each container lives in a world of its own and is by default not connected to anyone else. With the port mappings you allow inbound traffic on a certain defined port, but not vice versa. This will be addressed in the next chapter “Network”.