‏ ‏ ‎ ‏ ‏ ‎

1. Grundlagen

1.1. Warum eigene Docker-Images bauen?

  • Docker-Images können aus Docker-Registries gepullt werden

  • Oft ist es aber sinnvoll, ein eigenes Image zu bauen.

  • Man kann so seine Applikation in einem Image verpacken, um sie überall lauffähig zu machen, ohne sich um die Umgebung kümmern zu müssen.

1.2. Lab

1.2.1. Aufgabe: Hello World Image

  • Erstellen Sie ein verzeichnis hello-world und wechseln Sie in dieses Verzeichnis.

  • Erstellen Sie eine Datei hello2.txt mit dem Inhalt "Hello World!".

  • Erstellen Sie ein minimalistisches Dockerfile, das ein Basisimage (ubuntu:latest) verwendet. *

    FROM ubuntu:latest
    RUN apt update
    RUN apt dist-upgrade -y
    RUN echo Hallo > greet.txt
    RUN ( . /etc/os-release ; echo $VERSION_ID )
    RUN ls -lah
    WORKDIR app2
    RUN ls -lah
    COPY *.txt .
    docker build -t hello-world .
    docker run --rm -it hello-world
  • Fragen:

    • Warum wird dieser Container mit -it (interaktives Terminal) gestartet?

      Antwort

      Dieser Container würde ohne -it sofort wieder beendet werden, da kein Kommando ausgeführt wird, das den Container am Leben hält. Mit -it können wir in den Container hineinsehen und interagieren, um die Ausgabe der Befehle zu sehen und zu verstehen, was im Container passiert.

    • Wie kann man sich alle Images ansehen?

    • Wie kann man sich alle Container ansehen?

    • Wie kann man sich die Schichten (Layers) eines Images ansehen?

      Antwort
      docker history hello-world
      result
      IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
      5cfde82af460   3 hours ago   COPY *.txt . # buildkit                         11B       buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c ls -lah # buildkit               0B        buildkit.dockerfile.v0
      <missing>      3 hours ago   WORKDIR /app2                                   0B        buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c ls -lah # buildkit               0B        buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c ( . /etc/os-release ; echo $V…   0B        buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c echo Hallo > greet.txt # buil…   6B        buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c apt dist-upgrade -y # buildkit   33.2MB    buildkit.dockerfile.v0
      <missing>      3 hours ago   RUN /bin/sh -c apt update # buildkit            62.2MB    buildkit.dockerfile.v0
      <missing>      3 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
      <missing>      3 weeks ago   /bin/sh -c #(nop) ADD file:6089c6bede9eca8ec…   101MB
      <missing>      3 weeks ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
      <missing>      3 weeks ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
      <missing>      3 weeks ago   /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B
      <missing>      3 weeks ago   /bin/sh -c #(nop)  ARG RELEASE                  0B
    • Wie kann man die Schichten eines Images reduzieren?

      Antwort

      Indem man mehrere RUN-Kommandos mit && verbindet, um so die Anzahl der Layer zu reduzieren. Es gibt auch andere Möglichkeiten, wie zB das Verwenden von Multi-Stage Builds, um unnötige Dateien und Schichten zu vermeiden.

      FROM ubuntu:latest
      RUN apt update && \
          apt dist-upgrade -y && \
          echo Hallo > greet.txt && \
          ( . /etc/os-release ; echo Ubuntu $VERSION ) && \
          ls -lah
      WORKDIR app2
      RUN ls -lah
      COPY *.txt .
      result
      IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
      17b2acc6929b   4 seconds ago   COPY *.txt . # buildkit                         12B       buildkit.dockerfile.v0
      <missing>      4 seconds ago   RUN /bin/sh -c ls -lah # buildkit               0B        buildkit.dockerfile.v0
      <missing>      4 seconds ago   WORKDIR /app2                                   0B        buildkit.dockerfile.v0
      <missing>      4 seconds ago   RUN /bin/sh -c apt update     && apt dist-up…   95.4MB    buildkit.dockerfile.v0
      <missing>      3 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
      <missing>      3 weeks ago     /bin/sh -c #(nop) ADD file:6089c6bede9eca8ec…   101MB
      <missing>      3 weeks ago     /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
      <missing>      3 weeks ago     /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B
      <missing>      3 weeks ago     /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B
      <missing>      3 weeks ago     /bin/sh -c #(nop)  ARG RELEASE                  0B

1.2.2. Abschließende Bemerkungen

  • Uns fällt auf, dass die Ausgaben von ls und echo nicht am Bildschirm ausgegeben werden, sondern in den Layern des Images gespeichert werden.

    • Das liegt daran, dass jedes RUN-Kommando in einem eigenen Layer ausgeführt wird und die Ausgabe dieses Kommandos in diesem Layer gespeichert wird. Wenn wir also mehrere RUN-Kommandos haben, werden die Ausgaben in den jeweiligen Layern gespeichert und nicht am Bildschirm ausgegeben.

    • Abhilfe schafft hier die Verwendung von --progress=plain --no-cache beim Builden des Images, um die Ausgaben der RUN-Kommandos am Bildschirm zu sehen.

      docker build --no-cache \
                   --progress=plain \
                   -f Dockerfile2 \
                   -t hello-world .
    • Diese Ausgabe ist besonders hilfreich, um zu verstehen, was in den einzelnen Layern passiert, und um Fehler zu debuggen.

  • Auch sollte man die Caches löschen, die zB bei der Verwendung von apt entstehen, da diese sonst in den Layern gespeichert werden und das Image unnötig groß machen. Hierfür gibt es mehrere Möglichkeiten, zB apt clean oder rm -rf /var/lib/apt/lists/* am Ende eines RUN-Kommandos, um die Caches zu löschen.

    Die optimierte Version des Dockerfiles mit Cache-Bereinigung und einem CMD, damit der Container nicht sofort wieder beendet wird.
    FROM ubuntu:26.04
    RUN apt update \
        && apt dist-upgrade -y \
        && echo Hallo > greet.txt \
        && apt clean \
        && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    WORKDIR /app2
    COPY *.txt .
    CMD ["/bin/bash"]
  • Auch wurde das Basisimage von ubuntu:latest auf ubuntu:26.04 geändert, um eine festgelegte Version zu verwenden und so die Reproduzierbarkeit des Images zu gewährleisten.

1.2.3. Zusammenfassung

  • Man benötigt eine Textdatei mit dem Namen Dockerfile (ohne extension). Wird eine andere Datei verwendet, muss diese mit -f angegeben werden, zB docker build -t hello-world -f Dockerfile.dev .

  • Wie in einem Script kann eine Umgebung für das Programm gebaut werden. Dadurch wird das Image so konfiguriert, dass am Ende die Applikation im docker-Container laufen wird.

  • Es gibt hierfür mehrere Kommandos, die wir nun kennenlernen werden.

build image
  • Jedes Kommando ist ein eigener Layer, daher werden RUN - Kommandos oft mit && verbunden, um die Anzahl der Layer zu reduzieren. → Minimize the number of layers

dockerfile kommandos

2. FROM

  • Zum Auswählen des 'Base Images*

  • Man kann hierfür ein Betriebssystem wählen zB u buntu, alpine oder bereits mit einer Applikation, wie zB nginx, mariadb, postgres, node, …​

  • gibt man keine docker-registry an, wird automatisch https://hub.docker.com verwendet

  • Die Syntax des FROM-Kommandos sieht wie folgt aus:

    FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
Mögliche Verwendung von FROM
FROM openjdk:20-jdk
FROM --platform=linux/arm64 20-jdk
FROM openjdk:latest
FROM openjdk:20-jdk
FROM --platform=linux/arm64 eclipse-temurin:20-jammy
HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

3. Beispiel 1

FROM openjdk:latest (1)
COPY ./src .  (2)
RUN javac HelloWorld.java (3)
CMD java HelloWorld (4)
1 Nur beim Entwickeln :latest verwenden
2 Kopiert alle Files from Ordner ./src in den aktuellen Ordner im Image (root)
3 RUN führt ein Kommando aus
4 CMD ist der eine Befehl, weswegen der Container gestartet wird.
Bauen des Images
 docker build -t hello-world .
Starten des Containers
docker run --name hello-world hello-world:latest
Ansehen des gestoppten Containers
docker container ls -a
Löschen des gestoppten Containers
docker container rm hello-world

4. Beispiel 2

  • Es wird nur das image gewechselt → Java auf Ubuntu

FROM eclipse-temurin:20-jammy
COPY ./src .
RUN javac HelloWorld.java
CMD java HelloWorld
  • Nun ist genau festgelegt

    • welches JDK verwendet wird → temurin 20

    • welches OS verwendet wird → Ubuntu 22.04 LTS Jammy Jellyfish

Bauen des Images
 docker build -t hello-world .
Starten des Containers
docker run --rm \  (1)
           --name hello-world hello-world:latest
1 rm …​ remove → der gestoppte Container wird automatisch gelöscht

5. Beispiel 3

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HTL</title>
</head>
<body>
<h1>My Personal Site</h1>
<p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit
</p>
<p>
   Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
</p>
</body>
</html>
Dockerfile
FROM ubuntu:22.04

MAINTAINER stuetz

RUN apt update
RUN apt install -y nginx
COPY index.html /var/www/html/index.nginx-debian.html

EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
# build the image
docker build -t my-webserver:v1.0 .

# start the container
docker run --rm \
           -p 8080:80 \
           --name my-web my-webserver:v1.0
  • Man würde eher ein fertiges nginx-image verwenden, als es zu bauen.

6. CMD

  • Jeder Docker Container führt nur EIN Kommando aus:

    • CMD oder

    • ENTRYPOINT

  • Alle .sh files die in /docker-entrypoint.d enthalten sind, werden automatisch beim Starten des Containers ausgeführt. [source]

Dockerfile
FROM ...
CMD ["tail", "-f", "/dev/null"]
  • Dieses Kommando verhindert, dass der Docker Container beendet wird.