Docker Tutorial [Teil 7 - Dockerfile (Grundlagen)]

Datum

Bisher haben wir fertige Images verwendet und alle, zur Erstellung eines Containers, notwendigen Optionen und Parameter in der Konsole angegeben.
Die Verwendung eines Dockerfile erlaubt das Erstellen eines eigenen benutzerdefinierten Images. Hierbei wird ein bereits bestehendes Image als Basis verwendet und mit verschiedenen Anweisungen und Befehlen entsprechend geändert und angepasst. Anschließend hat man ein eigenes Image, woraus wieder Container erstellt werden können.

Dokumentation Dockerfile: Dockerfile reference

Auf die Reihenfolge kommt es an

Man muss beim Verwenden eines Dockerfile darauf achten, dass die dort aufgeführten Schritte in der richtigen Reihenfolge angegeben werden, da docker das Dockerfile von oben nach unten abarbeitet. Wenn man also beispielsweise versucht auf einen Ordner zuzugreifen, welcher aber erst in einem späteren Schritt angelegt wird, so bricht der Bau des Images ab.

Bei jeder Ausführung eines im Dockerfile eingetragenen Befehls wird eine „Schicht“ (Layer) erstellt, was einem neuen Image entspricht. Dieses wird dann wieder für den nächsten Befehl verwendet, welcher ebenfalls eine weitere „Schicht“ (und neues Image) generiert.

Der Aufbau

Ein Dockerfile kann mit einem Kommentar beginnen, allerdings ist die erste richtige Anweisung immer FROM und gibt das zu verwendende Basis-Image an. Es kann sowohl ein Image von docker Hub als auch ein eigenes verwendet werden.
Anschließend können beliebig viele Anweisungen folgen, wobei Befehle (COPY, RUN, etc…) groß geschrieben werden und dahinter der/die entsprechende(n) Parameter folgen.

Beispiel eines Dockerfile:

FROM ubuntu:15.04  
COPY . /app  
RUN make /app  
CMD python /app/app.py
Der Ablauf wäre hierbei wie folgt:
  • Verwende das Image ubuntu mit dem Tag 15.04 (FROM)
  • Kopiere Dateien aus dem aktuellen Verzeichnis in das Image (COPY)
  • Führe den make Befehl aus (RUN)
  • Starte das Python-Script mit dem Python-Interpreter (CMD)

docker hat zur Verwendung von Dockerfiles auch eine Best practice Dokumentation, welche man sich mal näher anschauen sollte.

Die Befehle

Ich werde hier nicht auf alle, sondern nur auf die (vermeintlich) am meist verwendeten Befehle eingehen.

FROM
  • muss immer als erstes im Dockerfile angegeben werden
  • gibt das „Basis-Image“ an, welches durch die nachfolgenden Befehle angepasst und geändert wird
RUN
Den Befehl RUN kann man auf zwei Arten im Dockerfile angeben:
  • RUN <Kommando> wird innerhalb des Containers zu /bin/sh -c <Kommando>
  • RUN ["executable", "param1", "param2"]

Um nun beispielsweise eine andere Shell als /bin/sh zu verwenden kann man folgendes verwenden: RUN ["/bin/bash", "-c", "echo hello"]
Anmerkung: Hierbei gilt es zu beachten, dass ein JSON Array ausgelesen wird und somit doppelte Anführungszeichen " anstatt einzelner ' verwendet werden müssen.

Man kann beim RUN Befehl, genau wie bei der Shell auch, mehrere Befehle mit && verknüpfen um die Anzahl an „Zwischenimages“ möglichst gering zu halten.

Beispiel:

FROM centos:centos7
RUN yum install epel-release -y && \
    yum update -y && \
    yum install redis -y && \
    yum clean all
EXPOSE 6379
ENTRYPOINT ["/usr/bin/redis-server"]
CMD
Den Befehl CMD kann man auf drei Arten im Dockerfile angeben:
  • CMD ["executable","param1","param2"] ist die (laut Dokumentation) bevorzugte Form
  • CMD ["param1","param2"] Standardwert für ENTRYPOINT
  • CMD command param1 param2 wird auch shell Form genannt und führt Kommandos per /bin/sh -c aus (ähnlich RUN)

Wichtig: CMD kann pro Dockerfile immer nur ein Mal angegeben werden. Wird CMD mehrfach in einem Dockerfile angegeben, so wird nur das letzte Vorkommen verarbeitet.

Beispiel (shell Form):

FROM ubuntu
CMD echo "This is a test." | wc -

Möchte man eine ausführbare Datei ohne eine Shell starten muss man den entsprechenden Befehl als HSON Array verwenden und den vollen Pfad zur entsprechenden Datei angeben.

Beispiel:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]
LABEL

Möchte man Metadaten zu einem Image hinzufügen, so sollte man den LABEL Befehl verwenden. Ein LABEL ist immer ein Key-Value Paar.

Beispiele:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL version="1.0"
LABEL multi.label1="value1" multi.label2="value2" other="value3"

Die entsprechenden Metadaten kann dann per docker inspect Befehl einsehen.

EXPOSE

EXPOSE gibt an, das der spätere Container zur Laufzeit auf einen (oder mehere) Port(s) lauschen soll.
Siehe auch: Docker Tutorial [Teil 4 – Ports / Portmapping]

EXPOSE <port>
Hierbei wird tcp als Standardprotokoll verwendet.

Beispiel für udp Port(s):
EXPOSE 80/udp

Die Angaben des EXPOSE Befehl im *Dockerfile@ können können zur Laufzeit mit der -p Option überschrieben werden:
docker run -p 80:80/tcp -p 80:80/udp ...

ENV

Mit Hilfe des ENV Befehls können Umgebungsvariablen erstellt und verändert werden. Auch hierbei handelt es sich wieder um Key-Value Paare.
1.) ENV <key> <value> setzt eine Variable samt Wert
Beispiel:

ENV myName John Doe
ENV myCat fluffy

2.) ENV <key>=<value> ... erlaubt das setzen mehrerer Variablen zur gleichen Zeit
Beispiel:
ENV myName="John Doe" myCat=fluffy

ARG

Seit Docker 1.9 gibt es die Möglichkeit einer einfachen Parametrisierung innerhalb des Dockerfiles. Dazu werden im Dockerfile mit ARG Parameter deklariert, die dann im weiteren Verlauf des Dockerfiles bei bestimmten Anweisungen benutzt werden können.

Beispiel:

ARG uid
USER ${uid:-wwwrun}

Hier wird ein Parameter uid deklariert, der dann beim USER Befehl benutzt wird. Dabei wird ein Default-Wert wwwrun verwendet, falls der Parameter von außen nicht gesetzt wird.

Um den Parameter beim Bauen des Images zu setzen, verwendet man das Argument --build-arg:

docker build --build-arg uid=daemon ...

weitere Befehle

Neben den hier bereits erwähnten Befehlen gibt es noch viele weitere (z.B.: ADD, COPY, etc…), welche allerdings den Rahmen dieses Tutorials sprengen würden. Allein der ADD Befehl bietet unterschiedliche Möglichkeiten Dateien entweder von einem Remote-System (per URL) oder lokal in das Image zu kopieren.

Wer mehr dazu wissen möchte, sollte einfach mal die Dokumentation und die Best practice schauen.

ein Image bauen

wenn das Dockerfile fertig und man daraus ein Image bauen möchte, so verwendet man docker build <Pfad/zum/Dockerfile>
Um die Images später besser unterscheiden zu können sollte man per -t <Name:Tag> entsprechende Imagenamen und Tags setzen.

Beispiel:

$ docker build -t entrypoint_testimage:0.1 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM centos:latest
 ---> 9f38484d220f
Step 2/3 : ENTRYPOINT ["sleep"]
 ---> Using cache
 ---> 3eb1462e75eb
Step 3/3 : CMD ["3"]
 ---> Using cache
 ---> 8103cf788600
Successfully built 8103cf788600
Successfully tagged entrypoint_testimage:0.1
$ docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
entrypoint_testimage   0.1                 8103cf788600        40 seconds ago      202MB

Daraus lassen sich dann beliebig viele Container bauen…
docker container run -d --name buildtest01 entrypoint_testimage:0.1

$ docker ps
CONTAINER ID        IMAGE                      COMMAND             CREATED             STATUS              PORTS               NAMES
3b4fdfde091e        entrypoint_testimage:0.1   "sleep 3"           4 seconds ago       Up 2 seconds                            buildtest01
Remote Dockerfile

Ein weiterer Vorteil von Dockerfiles ist, dass diese nicht einmal lokal auf dem System liegen müssen.
Man kann sie z.B. auf Git(lab/hub), einen privaten Server, etc. ablegen oder sogar bei pastebin.com und dann damit ein entsprechendes Image bauen:

$ docker image build -t remote_test01 https://pastebin.com/raw/ajiP6rqk
Downloading build context from remote url: https://pastebin.com/raw/ajiP6rqk     228B
Sending build context to Docker daemon  2.048kB
Step 1/7 : FROM golang as build_image
latest: Pulling from library/golang
6f2f362378c5: Pull complete
...
$ REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
remote_test01       latest              152f9514cccb        3 minutes ago       24.6MB
...

.dockerignore

Gerade bei Befehlen wie COPY, ADD, etc. wird manchmal der Inhalt eines Ordners in einen Container kopiert. Dabei will man vielleicht nicht das bestimmte Dateien, welche sich in dem entsprechenden Verzeichnis befinden, mit in den Container kopiert werden. Z.B. Test- und Debugging-Logs, git-Ordner, etc….

Hier kann man in das Verzeichnis, in welchem sich das Dockerfile befindet eine Datei namens .dockerignore packen und (auch per Wildcard) bestimmte Dateien ausschließen.

Das geschieht durch eine einfache Auflistung.

Beispiel:

.dockerignore:

*/*.md
*/.git
*/tests/

Autor
Kategorien container, docker

PRTG Map