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 Tag15.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 BefehlRUN
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 BefehlCMD
kann man auf drei Arten im Dockerfile angeben:
CMD ["executable","param1","param2"]
ist die (laut Dokumentation) bevorzugte FormCMD ["param1","param2"]
Standardwert fürENTRYPOINT
CMD command param1 param2
wird auch shell Form genannt und führt Kommandos per/bin/sh -c
aus (ähnlichRUN
)
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/