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 2 years 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 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 Jan 27 14:31 Dockerfile-todoui
-rw-rw-r-- 1 novatec novatec 213 Jan 27 14:31 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 49.6s (9/9) FINISHED docker:default
=> [internal] load build definition from Dockerfile-todobackend 0.6s
=> => transferring dockerfile: 264B 0.0s
=> [internal] load metadata for docker.io/library/eclipse-temurin:17-alpine 2.2s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [1/4] FROM docker.io/library/eclipse-temurin:17-alpine@sha256:eaf56b7430cee6c93871106367715e2675192093d8f67dbbdbe07136f7cfae60 33.4s
=> => resolve docker.io/library/eclipse-temurin:17-alpine@sha256:eaf56b7430cee6c93871106367715e2675192093d8f67dbbdbe07136f7cfae60 0.5s
=> => sha256:eaf56b7430cee6c93871106367715e2675192093d8f67dbbdbe07136f7cfae60 1.38kB / 1.38kB 0.0s
=> => sha256:1595d8e88965c1d9b8c1591282d0c2089a370136738ea21e0d393a94029aa6a7 1.95kB / 1.95kB 0.0s
=> => sha256:1d00d209bc23d75e71ec080f3dc771f1b3a69d1efeaf77770ca05ef0e8d32d28 4.21kB / 4.21kB 0.0s
=> => sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b 3.80MB / 3.80MB 0.6s
=> => sha256:2da2b7fa39d9ca89504884bfa364de57e68f656d1fd46f04941d3d34ffdbca30 21.11MB / 21.11MB 2.2s
=> => sha256:ecc9308f1b3f4d73b7c1a1aaba0b8eda9e9155caf3619e236d0c1a8b410704ef 143.99MB / 143.99MB 8.0s
=> => extracting sha256:2d35ebdb57d9971fea0cac1582aa78935adf8058b2cc32db163c98822e5dfa1b 2.9s
=> => sha256:b3851e26ddd2575c157cfd1ad2a673f8e8b4541dd5f6a0b8a2f65d3935e10fe6 129B / 129B 1.3s
=> => sha256:f03fcaf37379fff8027b1eaf41f78daa4bf57cd4f423a9e9d7015d3bbcf9bb53 2.28kB / 2.28kB 1.8s
=> => extracting sha256:2da2b7fa39d9ca89504884bfa364de57e68f656d1fd46f04941d3d34ffdbca30 5.7s
=> => extracting sha256:ecc9308f1b3f4d73b7c1a1aaba0b8eda9e9155caf3619e236d0c1a8b410704ef 11.2s
=> => extracting sha256:b3851e26ddd2575c157cfd1ad2a673f8e8b4541dd5f6a0b8a2f65d3935e10fe6 0.0s
=> => extracting sha256:f03fcaf37379fff8027b1eaf41f78daa4bf57cd4f423a9e9d7015d3bbcf9bb53 0.0s
=> [internal] load build context 8.5s
=> => transferring context: 53.87MB 5.8s
=> [2/4] RUN mkdir -p /opt/todobackend 4.6s
=> [3/4] WORKDIR /opt/todobackend 1.1s
=> [4/4] COPY todobackend/target/todobackend-0.0.1-SNAPSHOT.jar /opt/todobackend 2.5s
=> exporting to image 3.8s
=> => exporting layers 3.7s
=> => writing image sha256:b60ddc8d40d79eaca71e4f3e215434db73650c4cf60eed9fbf85140c9af3da71 0.0s
=> => naming to docker.io/library/todobackend:v0.1 0.0s- 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 b60ddc8d40d7 4 minutes ago 387MBDo 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)
...
2026-01-27T13:42:40.739Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
...
2026-01-27T13:43:07.521Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2026-01-27T13:43:07.679Z INFO 1 --- [ main] i.n.todobackend.TodobackendApplication : Started TodobackendApplication in 49.156 seconds (process running for 53.272)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.
2026-01-27 14:02:12.811 UTC [1] LOG: starting PostgreSQL 18.1 (Debian 18.1-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
2026-01-27 14:02:12.811 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2026-01-27 14:02:12.817 UTC [1] LOG: listening on IPv6 address "::", port 5432
2026-01-27 14:02:12.837 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2026-01-27 14:02:13.218 UTC [72] LOG: database system was shut down at 2026-01-27 14:02:12 UTC
2026-01-27 14:02:13.240 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
4206133bc2c6 postgres:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp postgresdb
47a2dc89af01 todoui:v0.1 "/__cacert_entrypoin…" 9 minutes ago Up 9 minutes 0.0.0.0:8090->8090/tcp, [::]:8090->8090/tcp todoui
e7b772393281 todobackend:v0.1 "/__cacert_entrypoin…" 21 minutes ago Up 21 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 db52bb42e640 15 minutes ago 356MB
todobackend v0.1 b60ddc8d40d7 28 minutes ago 387MB
hello-container v0.2 ad8119e1edac 39 minutes ago 138MB
postgres latest 019965b81888 2 weeks ago 456MB
novatec/technologyconsulting-hello-container v0.1 c4550e0a7743 2 years 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”.